题目链接
同P9868一样,后面可能会修改链接
直击题目
那我们开始吧
题目分析
嗯,这道题描述的还是比较清晰的,但是还可以再转化几步。
那怎么让U最小呢?我们好像从问题出发不了。我们要不先把问题晾在一边。
我们来看条件吧,我们要满足的是所有变量的最终值和初始值相等。
所以这道题其实是两个部分:
- 求出所有变量最终值的表示
- 给所有变量的初值赋值,让所有变量最终值和初值相等,而且U的变量最小
现在明显多了吧。
题目思路
前置知识
在这之前,如果你想看懂题目的代码,你需要会并查集(不会吧,不会吧,不会还有人不会并查集吧 )
当然,为了防止有人不会并查集,我依然决定真诚地把并查集的博客放在这里。
学会了吗?我们继续吧。
第一部分
我们按顺序先来解决求出所有变量最终值的表示这个问题。
为什么要加上“的表示”呢?因为我们一开始并不知道所有变量的初始值是什么样子才能让U的值最小并满足条件。
表示的构造
但是没有关系,我们可以把所有变量的初始值当成一个逻辑值。下面是我的构造:(先说好,第
i
i
i个变量的初始值记为
x
i
x_i
xi,目前值记为
y
i
y_i
yi)
如果第
i
i
i个值的目前的值根据赋值关系与
x
i
x_i
xi的初始值相同,那么
y
i
=
i
y_i=i
yi=i;
如果第
i
i
i个值的目前的值根据赋值关系与
x
i
x_i
xi的初始值相反,那么
y
i
=
−
i
y_i=-i
yi=−i;
如果第
i
i
i个值赋值为T,那么
y
i
=
(
n
+
1
)
y_i=(n+1)
yi=(n+1),因为T的反面是F,所以F的表示为
y
i
=
−
(
n
+
1
)
y_i=-(n+1)
yi=−(n+1);
如果第
i
i
i个值赋值为U,那么
y
i
=
0
y_i=0
yi=0,这样正好满足了
¬
U
=
U
¬U=U
¬U=U的性质。
赋值
那赋值就简单了。
x
i
=
x
j
⟺
y
i
=
y
j
x_i = x_j \iff y_i=y_j
xi=xj⟺yi=yj
x
i
=
¬
x
j
⟺
y
i
=
−
y
j
x_i =¬ x_j \iff y_i=-y_j
xi=¬xj⟺yi=−yj
x
i
=
T
⟺
y
i
=
(
n
+
1
)
x_i = T \iff y_i=(n+1)
xi=T⟺yi=(n+1)
x
i
=
F
⟺
y
i
=
−
(
n
+
1
)
x_i = F \iff y_i=-(n+1)
xi=F⟺yi=−(n+1)
x
i
=
U
⟺
y
i
=
0
x_i = U \iff y_i=0
xi=U⟺yi=0
初始化
最后别忘了初始化。
一开始的时候,
i
i
i的值就是
x
i
x_i
xi,所以
y
i
=
i
y_i=i
yi=i
整个过程实现的代码等到后面说完一起给。
第二部分
逐个考虑
我们现在可以考虑初始值了。假设第
i
i
i个值的最终值为
a
i
a_i
ai
如果
y
i
=
l
y_i=l
yi=l且
(
1
≤
∣
l
∣
≤
n
)
(1 \le |l| \le n)
(1≤∣l∣≤n),我们称为
i
i
i指向
l
l
l
先来热个身,我们来看看简单一点的情况。
如果
y
i
y_i
yi=0,那么第
i
i
i个值是
U
U
U,所以
x
i
=
U
x_i=U
xi=U
而T和F是同理的。
我们再来看看稍微难一点的情况。
如果
x
i
x_i
xi已经能确定是U,而存在
q
q
q满足
y
q
=
i
y_q=i
yq=i,那么
a
q
=
x
i
=
U
a_q=x_i=U
aq=xi=U
所以
x
q
=
U
x_q=U
xq=U了,对吧
T,F的情况也差不多,只不过T和F在取反的时候和U有一定差别
但实际上,我要给出一个思想,就是T和F只要不矛盾,可以理解为同一个值。也就是说T和F抵得上U的范围,因此我们可以把T和F在一起理解为一个和U范围一样的值。
我们再来看一个稍难的情况
m
m
m个值的关系正好围成了环,即
a
r
1
=
∣
x
r
2
∣
,
a
r
2
=
∣
x
r
3
∣
,
…
…
,
a
r
m
=
∣
x
r
1
∣
a_{r1}=|x_{r2}|,a_{r2}=|x_{r3}|,……,a_{rm}=|x_{r1}|
ar1=∣xr2∣,ar2=∣xr3∣,……,arm=∣xr1∣
这个时候我们可以直接判断这个环推出的是
a
r
1
=
¬
a
r
1
a_{r1}=¬a_{r1}
ar1=¬ar1还是
a
r
1
=
a
r
1
a_{r1}=a_{r1}
ar1=ar1
如果是前者,那么这个
a
r
1
=
U
a_{r1}=U
ar1=U,而整个环上的值都只能是
U
U
U了。
如果是后者,那么并不矛盾,整个环上的值都可以是T或F。
最难的情况就是还有别的值指向了这个环,当然它已经难不了多少了。
如果这个环上都是U,那么指向的值也只能是U。
否则,指向的值就可以是T或F。
那么怎么实现呢?当然是并查集!
并查集实现判断
有没有发现其实我们只需要标记必须是
U
U
U的节点就好了?
其实还能更少。
如果
p
p
p指向
q
q
q,那么按照并查集我们可以让
f
a
q
=
p
fa_q=p
faq=p
如果
p
,
q
p,q
p,q已经是一组的了,我们就判断他们之间矛不矛盾,矛盾就标记为
U
U
U,并上传给“队长”。
发现了吧,我们还需要维护当前结点与“队长”的值是相等还是相反,而这和我在并查集中所说的银河英雄传说那道题是一样的。
题目代码
#include<iostream>
#include<cstdlib>
#include<cstdio>
using namespace std;
int c,t,n,m;
int f[100010],ans;//f就是题解中的y
int g[100010],fa[100010];//g维护的是当前节点是否可以不是U
bool p[100010];//p维护的是当前节点与“队长”的值是否相等
int find(int x){
if(fa[x]==x) return x;
int qaq=find(fa[x]);//这里是为了先求出p[fa[x]]
p[x]^=p[fa[x]];
return fa[x]=qaq;
}
void unite(int x,int y){
fa[x]=y;
}
bool same(int x,int y){
return find(x)==find(y);
}
int main(){
cin>>c>>t;
for(int ca=1;ca<=t;ca++){
cin>>n>>m;
ans=0;
for(int i=1;i<=n;i++){
f[i]=i;
g[i]=-1;
fa[i]=i;
p[i]=0;
}
for(int i=1;i<=m;i++){
char ch;
int x,y;
cin>>ch;
if(ch=='+'){
cin>>x>>y;
f[x]=f[y];
}if(ch=='-'){
cin>>x>>y;
f[x]=-f[y];
}if(ch=='T'){
cin>>x;
f[x]=n+1;
}if(ch=='F'){
cin>>x;
f[x]=-(n+1);
}if(ch=='U'){
cin>>x;
f[x]=0;
}
}
for(int i=1;i<=n;i++){
if(f[i]==0) g[i]=0;
if(f[i]==(n+1)||f[i]==-(n+1)) g[i]=1;
if(f[i]==i) g[i]=1;
if(f[i]==-i) g[i]=0;
}
for(int i=1;i<=n;i++){
if(g[i]==-1){
int e=abs(f[i]);
if(!same(i,e)){
unite(i,e);
p[i]=(f[i]<0);
}else{
if(p[e]!=(f[i]<0)) g[i]=0;
//注意并查集中的队长一定是没有祖先的,所以一旦有重复,一定是队长
}
}
}
for(int i=1;i<=n;i++){
if(g[find(i)]==0) g[i]=0;
//如果一个集合的“队长”必须是U,那么整个集合都必须是U
}
for(int i=1;i<=n;i++){
if(g[i]==0) ans++;
}
cout<<ans<<endl;
}
return 0;
}