并查集
POJ2912 Rochambeau
题目大意:(剪子包袱锤~~)不过,多了个美丽的judge,可以随便出任何种手势。判断能否根据已知的游戏的结果选出judge。。。
思路:基本用食物链,枚举每个小孩为judge,判断他为judge时在第几句话出错falt[i](即到第几句话能判断该小孩不是judge)。
1. 如果只有1个小孩是judge时全部语句都是正确的,说明该小孩是judge,那么判断的句子数即为其他小孩的falt[i]的最大值。如果
2. 如果每个小孩的都不是judge(即都可以找到出错的语句),那么就是impossible。
3. 多于1个小孩是judge时没有找到出错的语句,就是Can not determine。
注意:小孩是从0开始编号的。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; struct use{ int a1,a2,kind; }a[2001]; int fa[1501]={0},b[501]={0}; char ch[100]; int rool(int x) { if (fa[x]!=x) fa[x]=rool(fa[x]); return fa[x]; } int main() { int i,j,r1,r2,n,m,ci,ans,l,ansn,judge,aa,bb,maxn; bool f; while (scanf("%d%d",&n,&m)==2) { if (m==0) printf("Player 0 can be determined to be the judge after 0 lines\n"); else { ans=0; memset(b,0,sizeof(b)); memset(a,0,sizeof(a)); for (i=1;i<=3*n;++i) fa[i]=i; for (i=1;i<=m;++i) { scanf("%s",&ch); l=strlen(ch); j=0; while (ch[j]>='0'&&ch[j]<='9') { a[i].a1=a[i].a1*10+ch[j]-'0'; ++j; } ++a[i].a1; if (ch[j]=='=') a[i].kind=0; if (ch[j]=='>') a[i].kind=1; if (ch[j]=='<') a[i].kind=-1; ++j; while (ch[j]>='0'&&ch[j]<='9'&&j<l) { a[i].a2=a[i].a2*10+ch[j]-'0'; ++j; } ++a[i].a2; } for (judge=1;judge<=n;++judge) { f=false; for (i=1;i<=3*n;++i) fa[i]=i; for (i=1;i<=m;++i) { if (a[i].a1==judge||a[i].a2==judge) continue; if (a[i].kind==0) { aa=a[i].a1; bb=a[i].a2; if (rool(aa+n)==rool(bb)||rool(aa+2*n)==rool(bb)) { b[judge]=i; f=true; break; } r1=rool(aa); r2=rool(bb); if (r1!=r2) { fa[r1]=r2; fa[rool(aa+n)]=rool(bb+n); fa[rool(aa+2*n)]=rool(bb+2*n); } } else { if (a[i].kind==1) { aa=a[i].a1; bb=a[i].a2; } else { aa=a[i].a2; bb=a[i].a1; } if (rool(aa)==rool(bb)||rool(aa+2*n)==rool(bb)) { b[judge]=i; f=true; break; } fa[rool(aa+n)]=rool(bb); fa[rool(aa+2*n)]=rool(bb+n); fa[rool(aa)]=rool(bb+2*n); } } if (!f) { ++ans; ansn=judge; } } if (ans>1) printf("Can not determine\n"); if (ans==0) printf("Impossible\n"); if (ans==1) { maxn=0; for (i=1;i<=n;++i) if (b[i]>maxn) maxn=b[i]; printf("Player %d can be determined to be the judge after %d lines\n",ansn-1,maxn); } } } }
POJ2236 Wireless Network
(距离是欧拉距离,用平方保存就好了,注意d要先平方)
#include<iostream> #include<cstdio> using namespace std; int dis[1010][1010]={0},ad[1010][2]={0},fa[1010]={0}; bool visit[1010]={false}; int rool(int x) { if (fa[x]!=x) fa[x]=rool(fa[x]); return fa[x]; } int main() { int n,i,j,r1,r2,d,a,b; char kind; cin>>n>>d; d=d*d; for (i=1;i<=n;++i) fa[i]=i; for (i=1;i<=n;++i) { scanf("%d%d",&ad[i][0],&ad[i][1]); for (j=1;j<i;++j) dis[j][i]=dis[i][j]=(ad[i][0]-ad[j][0])*(ad[i][0]-ad[j][0])+ (ad[i][1]-ad[j][1])*(ad[i][1]-ad[j][1]); } while (scanf("%*c%c",&kind)==1) { if (kind=='O') { scanf("%d",&a); r1=rool(a); for (i=1;i<=n;++i) if (dis[i][a]<=d&&visit[i]) { r2=rool(i); if (r1!=r2) fa[r2]=r1; } visit[a]=true; } else { scanf("%d%d",&a,&b); r1=rool(a); r2=rool(b); if (r1!=r2) printf("%s\n","FAIL"); else printf("%s\n","SUCCESS"); } } }
POJ2492 A Bug's Life
题目大意:n个昆虫,m组关系,每组关系给出的两个昆虫属于不同的性别,判断是否有Suspicious。
思路:类似与团伙的题目,简单并查集的应用。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int fa[2001]={0},enemy[2001]={0}; int rool(int x) { if (fa[x]!=x) fa[x]=rool(fa[x]); return fa[x]; } int main() { int i,j,t,n,m,ci,a,b,r1,r2; bool f; cin>>t; for (ci=1;ci<=t;++ci) { scanf("%d%d",&n,&m); memset(enemy,0,sizeof(enemy)); for (i=1;i<=n;++i) fa[i]=i; f=false; for (i=1;i<=m;++i) { scanf("%d%d",&a,&b); if (rool(a)==rool(b)) f=true; if (!f) { if (enemy[a]==0) enemy[a]=b; else { r1=rool(enemy[a]); r2=rool(b); fa[r1]=r2; } if (enemy[b]==0) enemy[b]=a; else { r1=rool(a); r2=rool(enemy[b]); fa[r1]=r2; } } } printf("%s%d%s\n","Scenario #",ci,":"); if (!f) printf("%s\n\n","No suspicious bugs found!"); else printf("%s\n\n","Suspicious bugs found!"); } }
POJ1456 Supermarket
题目大意:多组测试数据,每组n个物品,给定价值和售出期限,要求在期限之前卖出商品,同一时刻只能售出一个商品,求能得到的最大价值。
思路:我们把连续的被占用的区间看成一个集合(子树),它的根结点为这个区间左边第一个未被占用的区间。先排序,然后每次判断Find(b[i])是否大于0,大于0说明左边还有未被占用的空间,则占用它,然后合并(rool(b[i]), rool(rool(b[i]) – 1)即可。同样这里我们规定只能左边的子树合并到右边的子树。
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; int fa[10001]={0}; struct use{ int va,ti; }a[10001]; int rool(int x) { if (fa[x]!=x) fa[x]=rool(fa[x]); return fa[x]; } int my_comp(const use &x,const use &y) { if (x.va>y.va) return 1; else { if (x.va==y.va&&x.ti<y.ti) return 1; else return 0; } } int main() { int n,i,j,r1,maxn; long long ans; while(scanf("%d",&n)==1) { ans=0; maxn=0; for (i=1;i<=n;++i) { scanf("%d%d",&a[i].va,&a[i].ti); if (a[i].ti>maxn) maxn=a[i].ti; } for (i=1;i<=maxn;++i) fa[i]=i; sort(a+1,a+n+1,my_comp); for (i=1;i<=n;++i) { r1=rool(a[i].ti); if (r1>0) { fa[r1]=rool(r1-1); ans=ans+a[i].va; } } printf("%lld\n",ans); } }
POJ1733 Parity game(codevs上的奇偶游戏,vijos上的小胖的奇偶)
题目大意:给定一个长度为L的01串,告诉你一定区间(闭区间)内1的个数是even还是odd。然后判断到那句话时出了错。
思路:用并查集,保存结点到根的奇偶情况(用数字相加,最后%2就好了),注意路径压缩的时候要处理。每次对一条边的两点进行合并的时候要注意公式判断。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int fa[10001]={0},a[10001]={0},b[10001]={0},tot[10001]={0},kind[10001]={0}; int rool(int x) { int j; if (fa[x]!=x) { j=rool(fa[x]); tot[x]=tot[x]+tot[fa[x]]; fa[x]=j; } return fa[x]; } int main() { int size,i,j,n,m,r1,r2,x,y; bool f=false; char ch[10]; scanf("%d%d",&n,&m); for (i=1;i<=m;++i) { scanf("%d%d%s",&a[i*2-1],&a[i*2],&ch); if (ch[0]=='e') kind[i]=0; else kind[i]=1; ++a[i*2]; b[i*2-1]=a[i*2-1];b[i*2]=a[i*2]; } for (i=1;i<=m*2;++i) fa[i]=i; sort(b+1,b+m*2+1); size=unique(b+1,b+m*2+1)-b-1; for (i=1;i<=2*m;++i) a[i]=upper_bound(b+1,b+size+1,a[i])-b-1; for (i=1;i<=m;++i) { x=a[i*2-1]; y=a[i*2]; r1=rool(x); r2=rool(y); if (r1!=r2) { fa[r1]=r2; tot[r1]=tot[y]+kind[i]-tot[x]; } else { if (abs(tot[x]-tot[y])%2!=kind[i]) { f=true; cout<<i-1<<endl; break; } } } if (!f) cout<<m<<endl; }
POJ1984 Navigation Nightmare
题目大意:在1~m的时间里,每秒给两点、两点间的路长、路的东西南北。之后的k行中给出了k个询问,相应询问给出了两个节点和询问时间(不一定按升序排列),如果能到,则输出曼哈顿距离,否则输出-1。
思路:离线操作,对于访问时间排升序(置一个top指针,跟着边走),最后输出的时候注意按输入顺序输出就好了。对点进行并查集,但是tot数组要保存横向和纵向的位移(有正负)。规定向南和向东为正方向,然后就是对于合并时的结点更新。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; struct use{ int st,en,ti,yu; }a[40001]; struct user{ int st,en,va; char di; }b[40001]; int fa[40001]={0},sum[40001][2]={0},ans[40001]={0}; int my_comp(const use &x,const use &y) { if (x.ti<y.ti) return 1; else return 0; } int rool(int x) { int j; if (fa[x]!=x) { j=rool(fa[x]); sum[x][0]=sum[x][0]+sum[fa[x]][0]; sum[x][1]=sum[x][1]+sum[fa[x]][1]; fa[x]=j; } return fa[x]; } int main() { int i,j,n,m,k,r1,r2,t,top; char ch; scanf("%d%d",&n,&m); for (i=1;i<=m;++i) { scanf("%d%d%d",&b[i].st,&b[i].en,&b[i].va); cin>>b[i].di; if (b[i].di=='E'||b[i].di=='N') b[i].va=-1*b[i].va; } scanf("%d",&k); for (i=1;i<=k;++i) { scanf("%d%d%d",&a[i].st,&a[i].en,&a[i].ti); a[i].yu=i; } sort(a+1,a+k+1,my_comp); top=1; for (i=1;i<=n;++i) fa[i]=i; for (i=1;i<=m;++i) { r1=rool(b[i].st); r2=rool(b[i].en); if (r1!=r2) { if (b[i].di=='E'||b[i].di=='W') { sum[r2][0]=sum[b[i].st][0]-sum[b[i].en][0]-b[i].va; sum[r2][1]=sum[b[i].st][1]-sum[b[i].en][1]; } else { sum[r2][0]=sum[b[i].st][0]-sum[b[i].en][0]; sum[r2][1]=sum[b[i].st][1]-sum[b[i].en][1]-b[i].va; } fa[r2]=r1; } if (top>k) break; while (a[top].ti==i&&top<=k) { r1=rool(a[top].st); r2=rool(a[top].en); if (r1!=r2) ans[a[top].yu]=-1; else { ans[a[top].yu]=abs(sum[a[top].st][0]-sum[a[top].en][0]) +abs(sum[a[top].st][1]-sum[a[top].en][1]); } ++top; } } for (i=1;i<=k;++i) printf("%d\n",ans[i]); }
CODEVS3372 选学霸
题目描述 Description
老师想从N名学生中选M人当学霸,但有K对人实力相当,如果实力相当的人中,一部分被选上,另一部分没有,同学们就会抗议。所以老师想请你帮他求出他该选多少学霸,才能既不让同学们抗议,又与原来的M尽可能接近。
思路:将实力相当的人都并到一个集合中,统计出集合中元素的个数。然后做一个类似01背包(布尔型)的东西,找到最优解就可以了。
#include<cstdio> #include<iostream> using namespace std; int sum[30001]={0},fa[30001]={0},v[30001]={0}; bool visit[30001]={0},f[30001]={false}; int rool(int x) { if (fa[x]!=x) fa[x]=rool(fa[x]); return fa[x]; } int main() { int n,m,k,i,j,a,b,r1,r2,nn=0,maxn,minn; cin>>n>>m>>k; for (i=1;i<=n;++i) { fa[i]=i; sum[i]=1; } for (i=1;i<=k;++i) { cin>>a>>b; r1=rool(a); r2=rool(b); if (r1!=r2) { fa[r1]=r2; sum[r2]=sum[r2]+sum[r1]; } } for (i=1;i<=n;++i) { r1=rool(i); if (!visit[r1]) { ++nn; v[nn]=sum[r1]; visit[r1]=true; } } f[0]=true; for (i=1;i<=nn;++i) for (j=n;j>=v[i];--j) f[j]=f[j]||f[j-v[i]]; maxn=0; minn=n; for (i=m;i>=1;--i) if (f[i]) { maxn=i; break; } for (i=m+1;i<=n;++i) if (f[i]) { minn=i; break; } if (m-maxn<minn-m) cout<<maxn<<endl; if (m-maxn>minn-m) cout<<minn<<endl; if (m-maxn==minn-m) cout<<maxn<<endl; }
POJ1988 Cube Stacking
题目大意:(同银河舰队传说) 对于n块积木,进行两种操作:M a b 把a移到b上,C a 询问a下面有几块积木。
思路:用并查集和tot,sum数组维护。
#include<iostream> #include<cstdio> using namespace std; int fa[30001]={0},sum[30001]={0},len[30001]={0}; int rool(int x) { int j; if (fa[x]!=x) { j=rool(fa[x]); len[x]=len[x]+len[fa[x]]; fa[x]=j; } return fa[x]; } int main() { int p,n,i,j,r1,r2,x,y; char ch; scanf("%d",&p); n=30000; for (i=1;i<=n;++i) { fa[i]=i; sum[i]=1; } for (i=1;i<=p;++i) { scanf("%*c%c",&ch); if (ch=='M') { scanf("%d%d",&x,&y); r1=rool(x); r2=rool(y); if (r1!=r2) { len[r1]=len[r1]+sum[r2]; sum[r2]=sum[r2]+sum[r1]; fa[r1]=r2; } } else { scanf("%d",&x); r1=rool(x); printf("%d\n",len[x]); } } }
POJ1182 食物链
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
思路:把一个点虚化成三个点,a(本身),a+n(a吃的),a+2*n(吃a的)。然后每读入一个1操作,就判断a、b间有无吃与被吃的关系,然后合并a和b、a+n和b+n、a+2*n和b+2*n;每读入一个2操作,就判断a、b是否是同类和b是否吃a,然后合并a+n和b、a+2*n和b+n、a和b+2*n。注意要判断2)、3)两个条件。
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int fa[150001]={0},eat[150001]={0},eaten[150001]={0}; int rool(int x) { if (x==0) return x; if (fa[x]!=x) fa[x]=rool(fa[x]); return fa[x]; } int main() { int n,k,i,j,ans=0,kind,x,y; cin>>n>>k; for (i=1;i<=n*3;++i) fa[i]=i; for (i=1;i<=k;++i) { cin>>kind>>x>>y; if (x>n||y>n) { ++ans; continue; } if (kind==1) { if ((rool(x+n)==rool(y))||(rool(x+2*n)==rool(y))) { ++ans; continue; } fa[rool(x)]=rool(y); fa[rool(x+n)]=rool(y+n); fa[rool(x+2*n)]=rool(y+2*n); } if (kind==2) { if (rool(x)==rool(y)||rool(x+2*n)==rool(y)) { ++ans; continue; } fa[rool(x+n)]=rool(y); fa[rool(x+2*n)]=rool(y+n); fa[rool(x)]=rool(y+2*n); } } cout<<ans<<endl; }
POJ1308 Is It A Tree?
题目大意:给定一些点和边的关系,然后判断是否是一棵树。
思路:读入边的信息,然后将两个点合并,将后面点的fa给前面的点, 如两点已经在同一集合就不是树,最后判断是否是森林就可以了。
注意:森林是不可以的!
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int fa[100001]={0}; int visit[100001]={false},visitt[100001]={false}; int rool(int x) { if (fa[x]!=x) fa[x]=rool(fa[x]); return fa[x]; } int main() { int a,b,r1,r2,ci=0,i,t,maxn; bool f=false; while (cin>>a>>b) { ++ci; f=false; if (a==-1&&b==-1) break; for (i=0;i<=100000;++i) fa[i]=i; memset(visit,false,sizeof(visit)); memset(visitt,false,sizeof(visitt)); t=maxn=0; while (a!=0||b!=0) { if (a>maxn) maxn=a; if (b>maxn) maxn=b; visit[a]=visit[b]=true; r1=rool(a); r2=rool(b); if (r1==r2) f=true; fa[r2]=r1; cin>>a>>b; } if (!f) { for (i=1;i<=maxn;++i) if (visit[i]) { r1=rool(i); if (!visitt[r1]) { visitt[r1]=true; ++t; } } if (t>1) f=true; } cout<<"Case "<<ci<<" is "; if (f) cout<<"not "; cout<<"a tree."<<endl; } }
TYVJ1064 新三国争霸
描述
PP 特别喜欢玩即时战略类游戏,但他觉得那些游戏都有美中不足的地方。灾害总不降临道路,而只降临城市,而且道路不能被占领,没有保护粮草的真实性。于是他就研发了《新三国争霸》。
在这款游戏中,加入灾害对道路的影响(也就是一旦道路W[i,j]受到了灾害的影响,那么在一定时间内,这条路将不能通过)和道路的占领权(对于一条道路W[i,j],至少需要K[i,j]个士兵才能守住)。
PP可真是高手,不一会,就攻下了N-1座城市,加上原来的就有N座城市了,但他忽略了一点……那就是防守同样重要,不过现在还来的及。因为才打完仗所以很多城市都需要建设,PP估算了一下,大概需要T天。他现在无暇分身进攻了,只好在这T天内好好的搞建设了。所以他秒要派士兵占领一些道路,以确保任何两个城市之间都有路(不然敌人就要分而攻之了,是很危险的)。士兵可不是白干活的,每个士兵每天都要吃掉V的军粮。因为有灾害,所以方案可能有变化(每改变一次就需要K的军粮,初始方案也需要K的军粮)。
因为游戏是PP编的,所以他知道什么时候有灾害。PP可是一个很节约的人,他希望这T天在道路的防守上花最少的军粮。
N<=300,M<=5000 ,T<=50。
思路:把边的信息存下来,用了个二维、三维的数组,然后就是dp部分了,穷举每一次不变安排的开始和终结点,注意j(起点)要从i-1到0,因为要对边的取得情况进行判断,用了flag数组和||运算。然后就是最小生成树求边权和的问题了,用的并查集的做法,很简单就能的出tt,最后f(i)=min(f(i),f(j)+tt*(i-j)*v+k)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; struct uses{ int st,en,va; }tree[10000]; int road[500][500]={0},f[100],n,fa[500]; bool cn[500][500][100]={false},flag[500][500]={false}; int my_comp(const uses &x,const uses &y) { if (x.va<y.va) return 1; return 0; } int root(int x) { if (fa[x]!=x) fa[x]=root(fa[x]); return fa[x]; } int main() { int sum,m,t,v,k,i,j,a,b,c,x,y,t1,t2,p,tot=0,ans,r2,r1; cin>>n>>m>>t>>v>>k; memset(f,127,sizeof(f)); for (i=1;i<=m;++i) { cin>>a>>b>>c; if (a>n||b>n) continue; road[a][b]=road[b][a]=c; } cin>>p; for (i=1;i<=p;++i) { cin>>x>>y>>t1>>t2; if (road[x][y]==0) continue; if (t1>t2) { a=t1;t1=t2;t2=a; } if (t1>t) continue; if (t2>t) t2=t; for (j=t1;j<=t2;++j) cn[x][y][j]=cn[y][x][j]=true; } f[0]=0;tot=0; for (i=1;i<n;++i) for (j=i+1;j<=n;++j) if (road[i][j]>0) { ++tot; tree[tot].st=i; tree[tot].en=j; tree[tot].va=road[i][j]; } sort(tree+1,tree+tot+1,my_comp); for (i=1;i<=t;++i) { memset(flag,false,sizeof(flag)); for (j=i-1;j>=0;--j) { sum=1; ans=0; for (a=1;a<=n;++a) fa[a]=a; for (a=1;a<=tot;++a) { flag[tree[a].st][tree[a].en]= flag[tree[a].st][tree[a].en]||(cn[tree[a].st][tree[a].en][j+1]); if (!flag[tree[a].st][tree[a].en]) { r1=root(tree[a].st); r2=root(tree[a].en); if (r1!=r2) { fa[r1]=r2; ans=ans+tree[a].va; ++sum; } } if (sum==n) break; } if (sum>=n) f[i]=min(f[i],f[j]+ans*(i-j)*v+k); } } cout<<f[t]<<endl; }
bzoj2303 方格染色
题目大意:给定一个n*m的方格,填0/1,要求每个2*2的方格中的1的个数是奇数。其中k个位置的数已经确定,问方案数。
思路:可以发现如果第一行和第一列的数都确定了,整个表就确定了。可以枚举(1,1)的数,然后对于(i,j)要求a(i,j)^a(1,j)^a(i,1)^a(1,1)=1,所以可以用并查集维护一下有确定关系的,同时记录每个点到根的距离(!!!),方便判断一个联通块内的点是否有不满足条件的。没有确定数的联通块个数是x,答案就是2^x。
注意:合并的时候更新距离要注意。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 2000005 #define LL long long #define p 1000000000 using namespace std; struct use{int x,y,k;}ai[N]; int fa[N],n,m,kk,val[N],ww[N]; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} int idx(int x,int y){ if (y==1) return x-1; else return n+y-2;} int find(int x){ if (fa[x]==x) return fa[x]; int j=fa[x];fa[x]=find(fa[x]); ww[x]^=ww[j]; return fa[x];} int mi(LL x,int y){ LL a=1LL; for (;y;y>>=1){ if (y&1) a=a*x%p; x=x*x%p; }return a;} LL work(int x){ int i,ci,r1,r2,a,b,cnt; for (i=1;i<n+m-1;++i){ fa[i]=i;ww[i]=0;val[i]=-1; }for (i=1;i<=kk;++i){ ci=x^ai[i].k^((ai[i].x-1)*(ai[i].y-1)%2); a=idx(ai[i].x,1); b=idx(1,ai[i].y); if (ai[i].x==1&&ai[i].y==1){ if (ai[i].k!=x) return 0LL; else continue; }if (ai[i].x==1){ r1=ai[i].k; if (val[b]>=0){ if (val[b]!=r1) return 0LL; }else val[b]=r1; continue; }if (ai[i].y==1){ r1=ai[i].k; if (val[a]>=0){ if (val[a]!=r1) return 0LL; }else val[a]=r1; continue; }r1=find(a);r2=find(b); if (r1==r2){ if ((ww[a]^ww[b]^ci)>0) return 0LL; }else{ fa[r1]=r2;ww[r1]=(ww[a]^ww[b]^ci); } }for (i=1;i<n+m-1;++i){ if (val[i]<0) continue; r1=find(i); if (val[r1]<0) val[r1]=ww[i]^val[i]; else if ((ww[i]^val[i]^val[r1])>0) return 0LL; }for (cnt=0,i=1;i<n+m-1;++i) if (find(i)==i&&val[i]<0) ++cnt; return mi(2LL,cnt); } int main(){ int i;n=in();m=in();kk=in(); for (i=1;i<=kk;++i) ai[i]=(use){in(),in(),in()}; printf("%I64d\n",(work(0)+work(1))%p); }
bzoj4569 萌萌哒(!!!)
题目大意:求长度为n的数s的个数,有m个条件:要求sl1~sr1=sl2~sr2。
思路:st+并查集。fa[j][i]表示[i,i+(1<<j)-1]这个区间的父亲情况,每次合并l1~r1和l2~r2的时候,在st表上递归合并,如果已经是一个块的了,就可以return。因为st表中共有nlogn个点,所以总合并的次数也是nlogn的。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define up 20 #define LL long long #define p 1000000007 using namespace std; int fa[up][N],lo[N]; int find(int i,int u){return (fa[i][u]==u ? u :fa[i][u]=find(i,fa[i][u]));} int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} void merge(int d,int x,int y){ int r1,r2; r1=find(d,x);r2=find(d,y); if (r1==r2) return; fa[d][r1]=r2; if (!d) return;--d; merge(d,x,y);merge(d,x+(1<<d),y+(1<<d)); } int main(){ int i,j,n,m,l1,r1,l2,r2,cnt=0,ans;n=in();m=in(); for (j=0,i=1;i<=n;++i){ if ((1<<(j+1))<=i) ++j; lo[i]=j; }for (j=0;j<up;++j) for (i=1;i<=n;++i) fa[j][i]=i; for (i=1;i<=m;++i){ l1=in();r1=in(); l2=in();r2=in(); j=lo[r1-l1+1]; merge(j,l1,l2); merge(j,r1-(1<<j)+1,r2-(1<<j)+1); }for (i=1;i<=n;++i) if (find(0,i)==i) ++cnt; for (ans=9,i=2;i<=cnt;++i) ans=(int)((LL)ans*10LL%p); printf("%d\n",ans); }
(st表的思路非常厉害。注意到一层n个点的并查集合并最多n-1次)