将所有有关系的动物放到并查集中。维护一个带权并查集。
每个点带的权值是 off[u] ,表示它与它父亲的偏移量,这个值可以在路径压缩中更新。
偏移量为0表示同类,为1表示它吃它父亲,为2表示它父亲吃它,这样当偏移量不对的时候就可以判定是假话了。
#include<cstdio>
using namespace std;
const int N=50007;
int fa[N],off[N],n;
void init()
{
for(int i=1;i<=n;++i) fa[i]=i,off[i]=0;
}
int f(int x,int &d)
{
if(x==fa[x])
{
d=0;
return x;
}
fa[x]=f(fa[x],d);
d=(d+off[x])%3;
off[x]=d;
return fa[x];
}
//x与y的偏移量之差为0表示同类,为1表示吃y,为2表示被y吃
int main()
{
int m;
scanf("%d%d",&n,&m);
int ans=0,deep;
init();
while(m--)
{
int d,x,y;
scanf("%d%d%d",&d,&x,&y);
if(x>n||y>n) ++ans;
else if(d==2&&x==y) ++ans;
else
{
--d;
int rtx=f(x,deep);
int rty=f(y,deep);
if(rtx==rty)
{
if((off[x]-off[y]+3)%3!=d) ++ans;
}
else
{
fa[rtx]=rty;
off[rtx]=(off[rty]+d-off[x]+off[y]+3)%3;
}
}
}
printf("%d\n",ans);
return 0;
}
由于区间和可以表示前缀和相减的形式,因此我们维护前缀和之间的关系。若前缀和之间有关系,且与输入的不一样,那么++ans
就行了
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7;
int n,fa[N],p[N];
void init() { for(int i=0;i<=n;++i) fa[i]=i,p[i]=0; }
int f(int x)
{
if(x==fa[x]) return x;
int father=fa[x];
fa[x]=f(fa[x]);
p[x]+=p[father];
return fa[x];
}
int main()
{
int m;
while(~scanf("%d%d",&n,&m))
{
int ans=0;
init();
while(m--)
{
int l,r,d;
scanf("%d%d%d",&l,&r,&d);
--l;
int rtl=f(l);
int rtr=f(r);
if(rtl!=rtr)
{
p[rtr]=d-p[r]+p[l];
fa[rtr]=rtl;
}
else if(p[r]-p[l]!=d) ++ans;
}
printf("%d\n",ans);
}
return 0;
}
如果答案为 yes ,则x和y种类相同,否则x和y种类不同,用并查集维护,最后问题就转化成背包了。
注意数据 0 1 0
和 0 0 1
。
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N=607;
int fa[N],st[N],p1,p2,c[2][N],dp[N][2][N],pre[N][2][N];
vector<int> p[2][N],ans;
void init()
{
memset(c,0,sizeof(c));
ans.clear();
for(int i=1;i<=p1+p2;++i)
{
fa[i]=i;
st[i]=0;
p[0][i].clear();
p[1][i].clear();
}
}
int f(int x)
{
if(x!=fa[x])
{
int father=fa[x];
fa[x]=f(fa[x]);
st[x]=(st[x]+st[father])&1;
}
return fa[x];
}
int main()
{
int q;
while(~scanf("%d%d%d",&q,&p1,&p2))
{
if(q==0&&p1==0&&p2==0) break;
char s[10];
int x,y;
init();
while(q--)
{
scanf("%d%d%s",&x,&y,s);
int rtx=f(x),rty=f(y);
if(rtx==rty) continue;
st[rtx]=(st[y]-st[x]+(s[0]=='n')+2)&1;
fa[rtx]=rty;
}
for(int i=1;i<=p1+p2;++i)
{
int rt=f(i);
c[st[i]][rt]++;
p[st[i]][rt].push_back(i);
}
int n=0;
for(int i=1;i<=p1+p2;++i)
if(c[0][i]||c[1][i])
{
++n;
c[0][n]=c[0][i];
c[1][n]=c[1][i];
p[0][n]=p[0][i];
p[1][n]=p[1][i];
}
memset(dp,0,sizeof(dp));
dp[0][0][0]=1;
for(int i=1;i<=n;++i)
{
for(int k=0;k<2;++k)
{
for(int j=p1;j>=c[k][i];--j)
{
if(dp[i-1][0][j-c[k][i]]) dp[i][k][j]+=dp[i-1][0][j-c[k][i]],pre[i][k][j]=0;
if(dp[i-1][1][j-c[k][i]]) dp[i][k][j]+=dp[i-1][1][j-c[k][i]],pre[i][k][j]=1;
}
}
}
if(dp[n][0][p1]+dp[n][1][p1]==1)
{
int t=(dp[n][1][p1]==1);
for(int i=n;i>=1;--i)
{
for(int j=0;j<p[t][i].size();++j)
ans.push_back(p[t][i][j]);
int ppp=p1;
p1-=c[t][i];
t=pre[i][t][ppp];
}
}
sort(ans.begin(),ans.end());
for(int i=0;i<ans.size();++i)
printf("%d\n",ans[i]);
if(ans.size()||p1==0) puts("end");
else puts("no");
}
return 0;
}
带权并查集维护前缀和的奇偶性是否相同就行了
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=10007;
int fa[N],off[N],L[N],R[N],b[N],op[N];
void init(int n)
{
for(int i=0;i<n;++i) fa[i]=i,off[i]=0;
}
int f(int x)
{
if(x!=fa[x])
{
int father=fa[x];
fa[x]=f(fa[x]);
off[x]=(off[x]+off[father])&1;
}
return fa[x];
}
int main()
{
int n,q;
while(~scanf("%d",&n))
{
scanf("%d",&q);
char s[10];
int m=0;
for(int i=0;i<q;++i)
{
scanf("%d%d%s",&L[i],&R[i],s);
b[m++]=--L[i];
b[m++]=R[i];
op[i]=(s[0]=='o');
}
sort(b,b+m);
m=unique(b,b+m)-b;
init(m);
int ans=q;
for(int i=0;i<q;++i)
{
int l=lower_bound(b,b+m,L[i])-b;
int r=lower_bound(b,b+m,R[i])-b;
int rtl=f(l);
int rtr=f(r);
if(rtl==rtr)
{
if(((off[r]-off[l]+2)&1)^op[i])
{
ans=i;
break;
}
}
else
{
fa[rtr]=fa[rtl];
off[rtr]=(op[i]+off[l]-off[r]+2)&1;
}
}
printf("%d\n",ans);
}
return 0;
}
带权并查集维护点之间x坐标与y坐标之差就行了。
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
const int N=4e4+7;
int dir[4][2] = { {0,1},{0,-1},{1,0},{-1,0} };
int f1[N],f2[N],L[N],d[N],ans[N];
struct Query
{
int a,b,id;
Query(int a,int b,int id) : a(a) , b(b) , id(id) {}
Query(){}
};
vector<Query> q[N];
int fa[N],x[N],y[N];
int Abs(int x) { return x>0?x:-x; }
void init(int n)
{
for(int i=1;i<=n;++i)
{
fa[i]=i;
x[i]=y[i]=0;
}
}
int f(int k)
{
if(k!=fa[k])
{
int father=fa[k];
fa[k]=f(fa[k]);
x[k]+=x[father];
y[k]+=y[father];
}
return fa[k];
}
int main()
{
int n,m,k;
while(~scanf("%d%d",&n,&m))
{
char s[10];
init(n);
for(int i=1;i<=m;++i) q[i].clear();
for(int i=1;i<=m;++i)
{
scanf("%d%d%d%s",&f1[i],&f2[i],&L[i],&s);
if(s[0]=='E') d[i]=2;
else if(s[0]=='W') d[i]=3;
else if(s[0]=='N') d[i]=0;
else d[i]=1;
}
scanf("%d",&k);
for(int i=0;i<k;++i)
{
int a,b,t;
scanf("%d%d%d",&a,&b,&t);
q[t].push_back(Query(a,b,i));
}
for(int i=1;i<=m;++i)
{
int l=f1[i],r=f2[i];
int rtl=f(l);
int rtr=f(r);
if(rtl!=rtr)
{
fa[rtr]=rtl;
x[rtr]=(L[i]*dir[d[i]][0]+x[l]-x[r]);
y[rtr]=(L[i]*dir[d[i]][1]+y[l]-y[r]);
// printf("%d %d\n",x[rtr],y[rtr]);
}
for(int j=0;j<q[i].size();++j)
{
int a=q[i][j].a,b=q[i][j].b;
int rta=f(a);
int rtb=f(b);
if(rta!=rtb) ans[q[i][j].id]=-1;
else ans[q[i][j].id]=Abs(x[a]-x[b])+Abs(y[a]-y[b]);
}
}
for(int i=0;i<k;++i) printf("%d\n",ans[i]);
}
return 0;
}
这题可以用带权并查集做也可以用 u - v + n 表示不同性,u - v 表示同性这种方法来做。
#include<cstdio>
using namespace std;
const int N=4007;
int fa[N];
inline void init(int n){ for(int i=1;i<=n;++i) fa[i]=i; }
int f(int x) { return x==fa[x]?x:fa[x]=f(fa[x]); }
inline void Union(int x,int y ){ x=f(x);y=f(y);fa[x]=y; }
int main()
{
int T,kase=1;
scanf("%d",&T);
while(T--)
{
int n,m;
scanf("%d%d",&n,&m);
init(n<<1);
bool ok=true;
while(m--)
{
int u,v;
scanf("%d%d",&u,&v);
if(!ok) continue;
if(f(u)==f(v)) ok=false;
Union(u,v+n);
Union(u+n,v);
}
printf("Scenario #%d:\n%s\n\n",kase++,ok?"No suspicious bugs found!":"Suspicious bugs found!");
}
return 0;
}
一开始想着直接并查集,找出两个反例就输出裁判,最后还玄学特判了好多。。。还是too naive 。。 只要枚举每个裁判跑并查集就行了。
如果有唯一的裁判,那判断他的步数是使得所有其他人都不可能是裁判的最小的步数,其他情况按题意就行。
#include<cstdio>
#include<algorithm>
#include<set>
#include<cctype>
using namespace std;
const int N=507;
char s[100];
int fa[N],off[N],L[N<<2],R[N<<2],OP[N<<2];
void sep(int &u,int &v,int mid)
{
u=v=0;
for(int i=0;i<mid;++i) u=u*10+(s[i]-'0');
for(int i=mid+1;s[i]!='\0';++i) v=v*10+(s[i]-'0');
}
void init(int n)
{
for(int i=0;i<n;++i) fa[i]=i,off[i]=0;
}
int f(int x)
{
if(x!=fa[x])
{
int father=fa[x];
fa[x]=f(fa[x]);
off[x]=(off[father]+off[x])%3;
}
return fa[x];
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
for(int i=1;i<=m;++i)
{
scanf("%s",s);
for(int j=0;;++j)
{
if(!isdigit(s[j]))
{
sep(L[i],R[i],j);
OP[i]=s[j]=='='?0:(s[j]=='>'?1:2);
break;
}
}
}
int c=0,ans,tm=0;
for(int judge=0;judge<n;judge++)
{
init(n);
bool ok=true;
for(int i=1;i<=m;++i)
{
int x=L[i],y=R[i],op=OP[i];
if(x==judge||y==judge) continue;
int rtx=f(x);
int rty=f(y);
if(rtx!=rty)
{
off[rtx]=(op+off[y]-off[x]+3)%3;
fa[rtx]=rty;
}
else if((off[x]-off[y]+3)%3!=op)
{
tm=max(i,tm);
ok=false;
break;
}
}
if(ok) ++c,ans=judge;
}
if(c==1) printf("Player %d can be determined to be the judge after %d lines\n",ans,tm);
else if(c==0) puts("Impossible");
else puts("Can not determine");
}
return 0;
}
水
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
int fa[N],n;
bool vis[N];
void init(){ for(int i=1;i<N;++i) fa[i]=i,vis[i]=0; }
int f(int x) { return x==fa[x]?x:fa[x]=f(fa[x]); }
void Union(int x,int y){ x=f(x);y=f(y);fa[x]=y; }
void solve(int u,int v)
{
if(!vis[u]) vis[u]=true,++n;
if(!vis[v]) vis[v]=true,++n;
}
int main()
{
int u,v,m;
while(~scanf("%d%d",&u,&v))
{
if(u==-1&&v==-1) break;
if(u==0&&v==0)
{
puts("Yes");
continue;
}
bool ok=true;
m=n=0;
init(); ++m;
if(f(u)==f(v)) ok=false;
Union(u,v); solve(u,v);
while(~scanf("%d%d",&u,&v))
{
if(u==0&&v==0) break;
++m;
if(f(u)==f(v)) ok=false;
solve(u,v);
Union(u,v);
}
if(n!=m+1) ok=false;
puts(ok?"Yes":"No");
}
return 0;
}
倒着。。并查集
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+7;
typedef pair<int,int> pii;
int p[N],op[N*5],A[N*5],B[N*5],fa[N],id[N],mx[N],ans[N*5];
set<pii> s;
void init(int n)
{
s.clear();
for(int i=0;i<n;++i)
{
fa[i]=i;
mx[i]=p[i];
id[i]=i;
}
}
int f(int x) { return x==fa[x]?x:fa[x]=f(fa[x]); }
void Union(int x,int y)
{
x=f(x);
y=f(y);
if(mx[x]>mx[y])
{
mx[y]=mx[x];
id[y]=id[x];
}
else if(mx[x]==mx[y]) id[y]=min(id[y],id[x]);
fa[x]=y;
}
int main()
{
int n;
bool first=true;
while(~scanf("%d",&n))
{
if(first) first=false;
else puts("");
int m,q;
for(int i=0;i<n;++i) scanf("%d",&p[i]);
init(n);
scanf("%d",&m);
for(int i=0;i<m;++i)
{
int u,v;
scanf("%d%d",&u,&v);
s.insert(make_pair(min(u,v),max(u,v)));
}
scanf("%d",&q);
char str[100];
int a,b;
for(int i=0;i<q;++i)
{
scanf("%s%d",str,&a);
if(str[0]=='q') op[i]=0,A[i]=a;
else
{
scanf("%d",&b);
op[i]=1;
A[i]=a;B[i]=b;
s.erase(make_pair(min(a,b),max(a,b)));
}
}
for(set<pii>::iterator it=s.begin();it!=s.end();it++)
Union(it->first,it->second);
int c=0;
for(int i=q-1;i>=0;--i)
{
int a=A[i],b=B[i];
if(op[i]) Union(a,b);
else
{
if(mx[f(a)]>p[a]) ans[c++]=id[f(a)];
else ans[c++]=-1;
}
}
for(int i=c-1;i>=0;--i)
printf("%d\n",ans[i]);
}
return 0;
}
需要判定两种矛盾的情况:
- 之前相等,出现不等。可以直接有并查集维护,在同一个集合中则说明相等。
- 之前不等,出现相等。可以用set维护与当前集合不等的所有元素,合并时将小集合并到大集合复杂度是 O(nlogn) 。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
set<int> s[N];
int fa[N],ans[N],a[N],b[N],c[N];
void init(int l,int r)
{
for(int i=l;i<=r;++i)
{
fa[a[i]]=a[i]; fa[b[i]]=b[i];
s[a[i]].clear(); s[b[i]].clear();
}
}
int f(int x) { return x==fa[x]?x:fa[x]=f(fa[x]); }
void Union(int x,int y)
{
x=f(x);y=f(y);
if(s[x].size()<s[y].size()) swap(x,y);
fa[y]=x;
for(int v : s[y]) s[x].insert(v);
s[y].clear();
}
int main()
{
int n,m=0;
scanf("%d",&n);
ans[0]=0;
for(int i=0;i<N;++i) fa[i]=i;
for(int i=1;i<=n;++i) scanf("%d%d%d",&a[i],&b[i],&c[i]);
for(int i=1;i<=n;++i)
{
int x=f(a[i]);
int y=f(b[i]);
if(c[i])
{
if(x==y) continue;
else if(s[x].count(b[i]))
{
ans[++m]=i;
init(ans[m-1]+1,ans[m]);
}
else Union(x,y);
}
else
{
if(x==y)
{
ans[++m]=i;
init(ans[m-1]+1,ans[m]);
}
else
{
s[x].insert(b[i]);
s[y].insert(a[i]);
}
}
}
printf("%d\n",m);
for(int i=1;i<=m;++i)
printf("%d\n",ans[i]-ans[i-1]);
return 0;
}