[题解] Codeforces 468 E Permanent 折半,DP,图论
题目
建立一个二分图,左右各n个点,在左边的第x个点和右边的第y个点之间连一条权值为\(a_{x,y}\)的边。根据“积和式”的定义,我们是要在矩阵中选择n个位置,满足任意两个位置不共行、不共列,并计算所有选择方案的对应位置的值的积之和。显然,每一种选位置的方案对应这个二分图的一个完美匹配,我们现在要计算这个二分图每一个完美匹配的匹配边的边权乘积之和。左边和右边都有1e5个点,一共1e10条边,这是很多的;但是边权不是1的边只有50条,这就很好,考虑怎么略去边权为1的边的计算。对于每条\(a_{x,y}>1\)的边,令其中\(a_{x,y}-1\)条为特殊边,剩下1条为普通边。枚举所有选了特殊边的匹配点对,方案数\(\prod (a_{x,y}-1)\);剩下的每队匹配点就只有1种选择了,也就是选普通边,方案数\((n-左边的选择了特殊边的匹配点个数)\)!,因为左边剩下的点都可以在右边的未匹配点中任选一个,且每对只有1种方案。考虑仅由\(a_{x,y}>1\)的边构成的图,注意到每个连通块的方案数是不互相影响的,对于一个连通块,可以计算其中有\(i\)对点选择用特殊边匹配的方案数为\(f_i\),然后把每个连通块的f数组手动暴力卷积起来,最后乘上剩下节点数量的阶乘。
接下来对每个连通块分别计算。注意到连通块内点数是不超过边数+1的,所以一个连通块内最多51个点,记连通块内总点数为k。那么,左边和右边一定有一边的点数是\(\leq \frac k2\)的,假设左边是点少的那边。那么可以令\(dp_{i,msk}\)表示当前遍历到右边的第i个点,左边已经被匹配掉的点的集合是msk,的所有方案的贡献之和。转移就枚举右边第i个点和左边哪个点匹配,或者干脆不匹配。最后\(dp_{sz(l),msk}\)中msk二进制里1的个数就是匹配掉的点对数。由于一共50条边,所以i那维和转移的枚举合起来最多50次,这部分的总复杂度也就是\(50\cdot 2^{25}\),但是25还是比较大的(虽然这样好像能过?),我们可以在\(sz(l)<20\)的时候这么干,对其他情况采用另一种办法。
在\(sz(l)>19\)的时候,总点数也就至少是38,由于边数最多50,所以如果我们求出这个图的一个生成树,非树边最多13条。接下来就可以\(2^{13}\)次枚举非树边的选法,然后在生成树上进行一个树形dp。具体来说,\(dp_{i,j,0/1}\)表示以i为根的子树,子树内有j对点选了特殊边匹配,以及i是否被特殊边匹配占用。转移比较简单,需要在i的儿子中再跑一个背包。这部分复杂度是\(2^{13}\cdot 50^2\),50是平方不是3次方的原因是转移时另外跑一个背包的时候,枚举了i的一个儿子子树中选择的匹配点对数和其他前缀儿子中匹配点对数总和,但是发现树中每2个节点只会在他们的LCA处产生一次枚举。
这样把这两个做法结合一下就可以轻松通过了~
下面的代码有小错,在CF上是WA的,但是模拟赛第三方数据可以通过,大家先将就着看吧(
点击查看代码
#include <bits/stdc++.h>
#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <LL,LL>
#define pb push_back
#define fi first
#define se second
#define mpr make_pair
using namespace std;
const LL MOD=1e9+7;
LL n,k,fac[100010],val[100],dp[60][530000],mark[100],dp2[60][60][2],dp3[60][60][2],sz[60];
map <pii,LL> mp,te;
map <LL,LL> vs;
vector <pii> g[200010],gg[100];
vector <LL> res,l,r,all;
bool vis[200010];
vector <pair <pii,LL> > e,nte;
void dfs(LL pos)
{
if(pos<n) l.pb(pos);else r.pb(pos);
vis[pos]=true;
rep(i,g[pos].size())
{
e.pb(mpr(mpr(min(pos,g[pos][i].fi),max(pos,g[pos][i].fi)),g[pos][i].se));
if(!vis[g[pos][i].fi]) dfs(g[pos][i].fi),te[mpr(min(pos,g[pos][i].fi),max(pos,g[pos][i].fi))]=1;
}
}
vector <LL> convolution(vector <LL> a,vector <LL> b)
{
vector <LL> c(a.size()+b.size()-1,0);
rep(i,a.size()) rep(j,b.size()) (c[i+j]+=a[i]*b[j])%=MOD;
return c;
}
LL getid(LL x){return lower_bound(all.begin(),all.end(),x)-all.begin();}
void dfs2(LL pos,LL par)
{
vector <pii> son;
sz[pos]=1;
rep(i,gg[pos].size()) if(gg[pos][i].fi!=par)
{
dfs2(gg[pos][i].fi,pos);
son.pb(gg[pos][i]);sz[pos]+=sz[gg[pos][i].fi];
}
rep(i,son.size()+3) rep(j,all.size()+3) rep(k,2) dp3[i][j][k]=0;
dp3[0][0][0]=1;
rep(i,son.size()) rep(j,all.size()+1) rep(k,2) if(dp3[i][j][k]>0)
{
rep(jj,sz[son[i].fi]) rep(kk,2) if(dp2[son[i].fi][jj][kk]>0)
{
(dp3[i+1][j+jj][k]+=dp3[i][j][k]*dp2[son[i].fi][jj][kk])%=MOD;
if(k==0&&kk==0&&mark[pos]==0&&mark[son[i].fi]==0) (dp3[i+1][j+jj+1][1]+=dp3[i][j][k]*dp2[son[i].fi][jj][kk]%MOD*son[i].se)%=MOD;
}
}
rep(j,all.size()+1) rep(k,2) dp2[pos][j][k]=dp3[son.size()][j][k];
}
int main()
{
//freopen("c.in","r",stdin);
//freopen("c.out","w",stdout);
fac[0]=1;repn(i,100005) fac[i]=fac[i-1]*i%MOD;
cin>>n>>k;
LL x,y,z;
rep(i,k)
{
cin>>x>>y>>z;--x;--y;
mp[mpr(x,y)]=z;vs[x]=vs[y+n]=1;
}
for(auto it:mp)
{
g[it.fi.fi].pb(mpr(it.fi.se+n,it.se-1));
g[it.fi.se+n].pb(mpr(it.fi.fi,it.se-1));
}
res.pb(1);
for(auto it:vs) if(!vis[it.fi])
{
rep(i,99) val[i]=0;
e.clear();l.clear();r.clear();te.clear();
dfs(it.fi);
sort(e.begin(),e.end());e.erase(unique(e.begin(),e.end()),e.end());
if(min(l.size(),r.size())<=19)
{
if(r.size()<l.size()) swap(l,r);
rep(i,99) gg[i].clear();
rep(i,l.size())
{
rep(j,e.size()) if(e[j].fi.fi==l[i]||e[j].fi.se==l[i])
{
int v=e[j].fi.fi+e[j].fi.se-l[i],id;
rep(j,r.size()) if(r[j]==v) id=j;
gg[id].pb(mpr(i,e[j].se));
}
}
rep(j,59) rep(k,1<<l.size()) dp[j][k]=0;
dp[0][0]=1;
rep(i,r.size()) rep(k,1<<l.size()) if(dp[i][k]>0)
{
(dp[i+1][k]+=dp[i][k])%=MOD;
rep(p,gg[i].size()) if((k&(1<<gg[i][p].fi))==0) (dp[i+1][k|(1<<gg[i][p].fi)]+=dp[i][k]*gg[i][p].se)%=MOD;
}
rep(k,1<<l.size()) (val[__builtin_popcount(k)]+=dp[r.size()][k])%=MOD;
}
else
{
rep(i,99) gg[i].clear();
nte.clear();
all.clear();rep(i,l.size()) all.pb(l[i]);rep(i,r.size()) all.pb(r[i]);
sort(all.begin(),all.end());
rep(i,e.size())
{
if(te.find(e[i].fi)!=te.end())
{
LL u=getid(e[i].fi.fi),v=getid(e[i].fi.se);
gg[u].pb(mpr(v,e[i].se));gg[v].pb(mpr(u,e[i].se));
}
else nte.pb(mpr(mpr(getid(e[i].fi.fi),getid(e[i].fi.se)),e[i].se));
}
rep(i,all.size()) mark[i]=0;
rep(i,1<<nte.size())
{
LL addn=__builtin_popcount(i),mulv=1,bad=0;
rep(j,nte.size()) if((i&(1<<j))>0)
{
(mulv*=nte[j].se)%=MOD;
if(mark[nte[j].fi.fi]||mark[nte[j].fi.se]) bad=1;
mark[nte[j].fi.fi]=mark[nte[j].fi.se]=1;
}
if(bad) continue;
rep(j,all.size()) rep(k,all.size()) rep(p,2) dp2[j][k][p]=0;
dfs2(0,-1);
rep(j,all.size()+1) rep(k,2) if(dp2[0][j][k]>0) (val[j+addn]+=dp2[0][j][k]*mulv)%=MOD;
}
}
int lst=-1;rep(i,99) if(val[i]>0) lst=i;
if(lst==-1) continue;
vector <LL> tmp;rep(i,lst+1) tmp.pb(val[i]);
res=convolution(res,tmp);
}
LL ans=0;
rep(i,res.size()) (ans+=res[i]*fac[n-i])%=MOD;
cout<<ans<<endl;
return 0;
}
[题解] Codeforces 468 E Permanent 折半,DP,图论的更多相关文章
- 【题解】284E. Coin Troubles(dp+图论建模)
[题解]284E. Coin Troubles(dp+图论建模) 题意就是要你跑一个完全背包,但是要求背包的方案中有个数相对大小的限制 考虑一个\(c_i<c_j\)的限制,就是一个\(c_i\ ...
- DP&图论 DAY 6 下午 考试
DP&图论 DAY 6 下午 考试 样例输入 样例输出 题解 >50 pt dij 跑暴力 (Floyd太慢了QWQ O(n^3)) 枚举每个点作为起点,dijks ...
- Codeforces 909 C. Python Indentation (DP+树状数组优化)
题目链接:Python Indentation 题意: Python是没有大括号来标明语句块的,而是用严格的缩进来体现.现在有一种简化版的Python,只有两种语句: (1)'s'语句:Simple ...
- DP&图论 DAY 7 上午
DP&图论 DAY 7 上午 图论练习题 P2176 [USACO14FEB]路障Roadblock 先跑最短路(最多n条边,否则出环) 枚举每条边,加倍,再跑 dijkstra 取最大 ...
- DP&图论 DAY 6 上午
DP&图论 DAY 6 上午 双连通分量 从u-->v不存在必经边,点 点双连通分量 边双连通分量 点/边双连通分量缩点之后变成一个树 找连通块的时候不越过割点或者桥 P3469 [ ...
- DP&图论 DAY 5 下午
DP&图论 DAY 5 下午 树链剖分 每一条边要么属于重链要么轻边 证明: https://www.cnblogs.com/sagitta/p/5660749.html 轻边重链都是交 ...
- DP&图论 DAY 5 上午
DP&图论 DAY 5 上午 POJ 1125 Stockbroker Grapevine 有 N 个股票经济人可以互相传递消息,他们之间存在一些单向的通信路径.现在有一个消息要由某个人开 ...
- DP&图论 DAY 4 下午图论
DP&图论 DAY 4 下午 后天考试不考二分图,双联通 考拓扑排序 图论 图的基本模型 边: 有向边构成有向图 无向边构成无向图 权值: 1.无权 2.点权 3.边权 4.负权(dij不 ...
- DP&图论 DAY 4 上午
DP&图论 DAY 4 上午 概率与期望 概率◦某个事件A发生的可能性的大小,称之为事件A的概率,记作P(A).◦假设某事的所有可能结果有n种,每种结果都是等概率,事件A涵盖其中的m种,那 ...
随机推荐
- 图论学习笔记·$Floyd$ $Warshall$
对于图论--虽然本蒟蒻也才入门--于是有了这篇学习笔记\(qwq\) 一般我们对于最短路的处理,本蒟蒻之前都是通过构建二维数组的方式然后对每两个点进行1次深度或者广度优先搜索,即一共进行\(n\)^2 ...
- 21条最佳实践,全面保障 GitHub 使用安全
GitHub 是开发人员工作流程中不可或缺的一部分.无论你去哪个企业或开发团队,GitHub 都以某种形式存在.它被超过8300万开发人员,400万个组织和托管超过2亿个存储库使用.GitHub 是世 ...
- linux学习随笔2之防火墙
centos7默认使用的防火墙是firewalld 查看所有打开的端口: firewall-cmd --zone=public --list-ports 更新防火墙规则: firewall-cmd - ...
- devops-1:代码仓库git的使用
devops-gitlab 介绍 gitlab同github.gitee和bitbucket功能一致,都是提供一个存储代码的服务,这里就以gitlab为例,学习一下如何结合git工具使用. 核心组件: ...
- [算法1-排序](.NET源码学习)& LINQ & Lambda
[算法1-排序](.NET源码学习)& LINQ & Lambda 说起排序算法,在日常实际开发中我们基本不在意这些事情,有API不用不是没事找事嘛.但必要的基础还是需要了解掌握. 排 ...
- MySQL查询性能优化七种武器之索引潜水
有读者可能会一脸懵逼? 啥是索引潜水? 你给起的名字的吗?有没有索引蛙泳? 这个名字还真不是我起的,今天要讲的知识点就叫索引潜水(Index dive). 先要从一件怪事说起: 我先造点数据复现一下问 ...
- 搞定面试官 - 可以介绍一下在 MySQL 中你平时是怎么使用 COUNT() 的嘛?
大家好,我是程序员啊粥. 相信在大家的工作中,有很多的功能都需要用到 count(*) 来统计表中的数据行数.同时,对于一些大数据的表,用 count 都是瑟瑟发抖,往往会结合缓存等进行处理. 那么, ...
- CAD二次开发(.net)优秀网站分享
Autodesk官方网站 官方帮助文档:AutoCAD 2016 帮助: Managed .NET Developer's Guide (.NET) (autodesk.com) DXF帮助手册:DX ...
- 概述:基于事件的优化方法 / 事件驱动优化 / Event-Based Optimization / EBO
大家好,我是月出 本文基于这篇综述,介绍了 事件驱动优化(Event-Based Optimization, EBO). 事件驱动优化,是一种建模现实场景.做优化的思路,理论和 MDP / 强化学习很 ...
- Taurus.MVC 微服务框架 入门开发教程:项目部署:6、微服务应用程序Docker部署实现多开。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 开源地址:https://github.com/cyq1162/Taurus.MVC 本系列第一篇:Tauru ...