并查集

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);
   }
        }
}
} 
View Code

 

POJ2236 Wireless Network

 题目大意:一场地震毁了所有的电脑(我该说earthquake的威力大呢?还是电脑太RUO。。。)给出各电脑的坐标,两个电脑可以相连,当两电脑可以直接相连或者有一个中间电脑可以使之分别与中间电脑直接相连。两种操作:O ———修复电脑;S——查询两电脑能否连接,可以就输出“SUCCESS”,否则输出“FAIL”。
思路:预处理各电脑间的距离,简单的并查集,在线做法,每次修一台电脑都要穷举所有距离小于等于d的电脑,并更新。
(距离是欧拉距离,用平方保存就好了,注意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");
}
}
} 
View Code

 

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!");
}
} 
View Code

 

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);
}
}
View Code

 

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;
} 
View Code

 

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]);
} 
View Code

 

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;

}
View Code

 

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]);
        }
    }
} 
View Code

 

POJ1182 食物链

Description
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有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;
} 
View Code

 

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;
}
} 
View Code

 

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;
} 
View Code

 

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);
}
View Code

 

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);
}
View Code

(st表的思路非常厉害。注意到一层n个点的并查集合并最多n-1次)

posted @ 2016-04-12 21:38  Rivendell  阅读(664)  评论(0编辑  收藏  举报