标签:html 最小 round 星球大战 max pre 状压 方案 枚举
并查集
正向考虑的话,感觉不好操作已经合并了的并查集
但若反向考虑的话,就只用不断向图中加入新点——以及合并操作就好了
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#define MAXN 200005
using namespace std;
struct edge{
int to,next;
}e[MAXN*2];
int fa[MAXN*2],head[MAXN*2],d[MAXN*2],ans[MAXN*2];
bool vis[MAXN*2];
int n,m,cnt,ent=1,k;
void add(int u,int v){
e[ent]=(edge){v,head[u]};
head[u]=ent++;
}
int find(int x){
return x==fa[x]?x:fa[x]=find(fa[x]);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1,a,b;i<=m;i++){
scanf("%d%d",&a,&b); a++; b++;
add(a,b); add(b,a);
}
scanf("%d",&k); cnt=n-k;
for(int i=1;i<=k;i++) scanf("%d",&d[i]),d[i]++,vis[d[i]]=1;
for(int u=1;u<=n;u++) if(!vis[u]){
for(int i=head[u];i;i=e[i].next){
int v=e[i].to; if(vis[v]) continue;
int fu=find(u),fv=find(v);
if(fu==fv) continue;
cnt--; fa[fv]=fu;
}
}
for(int I=k;I>=1;I--){
ans[I]=cnt;
int u=d[I];
vis[u]=0; cnt++;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to; if(vis[v]) continue;
int fu=find(u),fv=find(v);
if(fu==fv) continue;
cnt--; fa[fv]=fu;
}
}
ans[0]=cnt;
for(int i=0;i<=k;i++) printf("%d\n",ans[i]);
return 0;
}
好题。
有一个性质(正权图):
对于一个无向图的每一种最小生成树,某种权值的边的数目是相同的
(形象点说:如果一个无向图有两种最小生成树的话,且第一种中有2个边权为5的边,
那么第二种最小生成树中也一定有2个边权为5的边)
可以通俗一点理解:
因为对一颗树来说,边的个数是固定的,为了保证生成树的边权和最小,
那么无论是哪一种最小的生成方式,对于某一种权值的边的数量一定是固定的,否则总边权就变了。
正常一点的来理解:
按照Kruskal算法,
先考虑权值最小的那些边,
这些边全部放入图中的话,也许会构成环,
无论删掉哪些边之后使得图中没有环,
最终联通的点的构成集合都是相同的。
因为每加一条边,联通块的个数就减少1个,且联通的点的集合相同,
所以连的边的个数相同的,
并且每种连边方案的效果(即对图的联通贡献)是相同的(不会受其他权值的边的影响)。
至于大一点权值的边,我们把上面联通的点缩为一个点,那么就和上面是一样的了
所以每种权值的边,无论选哪些来连,只要可以联通成功,那么所选的该权值的边的个数就是相同的。
解法:
先跑一个Kruskal最小生成树,统计出每种权值的边的数量
接下来枚举 选出的每种权值 (记当前枚举到的权值为w,其对应的选的数量为k),
把构成最小生成树的其他权值的边先联通它们该连通的那些部分,
再从权值==w的边集中暴力枚举出k个边,尝试把它们插入图中,看是否能联通整个图,
如果可以联通,则表明该权值的这k个边是可以是一种联通方法
统计出 每种权值的边 有多少种联通方法
(因为相同权值的边不超过10个,状压暴力枚举就好,那个Matrix-Tree什么的也不会)
最后把 选出的每种权值 的联通方法数组合(相乘)就好了。
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int mod=31011;
struct edge{
int u,v,w;
bool operator <(const edge &rtm) const{
return w<rtm.w;
}
}e[1005],use[105];
struct group{
int val,num;
}g[105];
int fa[105],ha[1050];
int n,m,sn,cnt,ans=1,p,ent;
void reset_father(){
sn=n;
for(int i=1;i<=n;i++) fa[i]=i;
}
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
bool merge(int i,edge *E){
int u=E[i].u,v=E[i].v;
int fu=find(u),fv=find(v);
if(fu==fv) return 0;
sn--; fa[fv]=fu;
return 1;
}
int doit(int cas){
static int l,r,now; now=0;
while(!p||e[p].w!=g[cas].val) p++; l=p;
while(p<=m&&e[p].w==g[cas].val) p++; r=p-1;
for(int s=0;s<(1<<(r-l+1));s++) if(ha[s]==g[cas].num){
reset_father();
for(int i=1;i<=ent;i++) if(use[i].w!=g[cas].val) merge(i,use);
for(int i=0;i<=r-l;i++) if((1<<i)&s) merge(l+i,e);
if(sn==1) now++;
}
return now;
}
int main(){
for(int i=1<<0;i<=1<<10;i++)
ha[i]=ha[i>>1]+(i&1);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
sort(e+1,e+m+1);
reset_father();
for(int i=1;i<=m;i++){
if(!merge(i,e)) continue;
if(!cnt||e[i].w!=g[cnt].val)
++cnt,g[cnt].val=e[i].w;
g[cnt].num++;
use[++ent]=e[i];
}
if(sn!=1) {printf("0"); return 0;}
for(int i=1;i<=cnt;i++){
int tmp=doit(i);
ans=1ll*ans*tmp%mod;
}
printf("%d",ans);
return 0;
}
标签:html 最小 round 星球大战 max pre 状压 方案 枚举
原文地址:http://www.cnblogs.com/zj75211/p/7652481.html