比赛时候想了好久,不会。看了官方题解才会......
Bond是极小割边集合,去掉一个Bond之后,只会将原图分成两个连通块。
假设某些点构成的集合为 s(点集中的点进行状压后得到的一个十进制数),那么剩下的点构成的集合为 t=(1<<n)-1-s
如果s是连通的,t也是连通的,那么跨越s、t集合的边的答案就+1(即跨越s、t集合的边构成一个Bond)。
因此,只需枚举 s 即可。
接下来问题转变成了以下两个问题:
1.如何判断某个点集合是否连通:状压DP预处理。
2.如何让跨越s、t集合的边的答案+1:如果每次遍历所有的边去加答案,时间复杂度爆炸O(m*(1<<n)),因此需要换一种思路:
我们可以计算出所有Bond有几个,然后减去(u,v)不跨越s,t的Bond个数就是(u,v)这条边的答案。
具体看代码吧~~,再贴上官方题解:
#pragma comment(linker, "/STACK:1024000000,1024000000") #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #include<map> #include<set> #include<queue> #include<stack> #include<iostream> using namespace std; typedef long long LL; const double pi=acos(-1.0),eps=1e-8; void File() { freopen("D:\\in.txt","r",stdin); freopen("D:\\out.txt","w",stdout); } inline int read() { char c = getchar(); while(!isdigit(c)) c = getchar(); int x = 0; while(isdigit(c)) { x = x * 10 + c - ‘0‘; c = getchar(); } return x; } int T,n,m,e[25],sum[(1<<20)+10],sz; int u[500],v[500]; bool f[(1<<20)+10]; void pre() { for(int i=0;i<n;i++) f[1<<i]=1; for(int i=0;i<(1<<n);i++) { if(!f[i]) continue; int st=0; for(int j=0;j<n;j++) if(i&(1<<j)) st=st|e[j]; for(int j=0;j<n;j++) { if(i&(1<<j)) continue; if(st&(1<<j)) f[i|(1<<j)]=1; } } } int main() { scanf("%d",&T); int cas=1; while(T--) { scanf("%d%d",&n,&m); memset(e,sz=0,sizeof e); memset(f,0,sizeof f); memset(sum,0,sizeof sum); for(int i=0;i<m;i++) { scanf("%d%d",&u[i],&v[i]); e[u[i]]=e[u[i]]|(1<<v[i]), e[v[i]]=e[v[i]]|(1<<u[i]); } pre(); for(int i=0;i<(1<<n);i++) { if(f[i]==0||f[(1<<n)-1-i]==0) continue; if(i>(1<<n)-1-i) break; sum[i]=1; sum[(1<<n)-1-i]=1; sz++; } for(int i=0;i<n;i++) { for(int j=(1<<n)-1;j>=0;j--) { if((1<<i)&j) continue; sum[j]=sum[j]+sum[(1<<i)|j]; } } printf("Case #%d:",cas++); for(int i=0;i<m;i++) printf(" %d",sz-sum[(1<<u[i])|(1<<v[i])]); printf("\n"); } return 0; }
时间: 2024-11-28 14:19:32