三值逻辑:有点坑并且细节较繁琐,但有点板子的并查集。
修改操作
发现对于每个点,只有对他的最后一次操作才是有用的,所以记录下最终的祖先即可。
然而这里并不能用并查集来实现,因为并查集它具有的是传递性,无论你路不路径压缩,每次修改一个父节点时它的子节点一定会被修改,所以我们不能使用并查集。
但是可以使用并查集相关思想。
首先我们建立虚拟源点 \(n+1\) 表示 \(T\),\(n+2\) 表示 \(U\)。到某个点的距离为奇数时表示与这个点的值相反,为偶数则表示与这个点的值相等。
每次修改后,我们都直接指向父节点的祖先节点。为啥祖先节点就可以呢?因为此时祖先节点是未被修改的,而根据题目中“使得每个变量初始值与最终值相同”,所以这里跟祖先节点连边相当于和祖先节点的初值连边。自然是正确的了。
upd:这里其实可以用并查集,但是要时时刻刻路径压缩,每 combine 一次就要压一次。
判断合法性
对于直接连向 \(U\) 的连通块,最终的结果一定是 \(U\)。
其余如果出现矛盾的连通块,最终的结果也一定全部是 \(U\)。这个的实现,只需要每次合并的时候判断一下到祖先节点的距离就好了。
时间复杂度 \(O(tn)\)。
代码
#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pi;
int n,m,tof[100005],tod[100005],f[100005],d[100005];
//n+1: T ,n+2: U
bitset<100005>ilg;
int findf(int x)
{if(f[x]!=x){int orif=f[x];f[x]=findf(f[x]);d[x]=(d[orif]^d[x]);}return f[x];
}
void combine(int x,int y,int td)
{int fx=findf(x),fy=findf(y);if(fx!=fy){d[fx]=(d[y]^td^d[x]);f[fx]=fy;}
}
void init()
{for(int i=1;i<=n+2;i++)f[i]=i,d[i]=0;
}
void solve()
{cin>>n>>m;for(int i=1;i<=n+2;i++){tof[i]=i;tod[i]=0;}ilg.reset();for(int i=1;i<=m;i++){int a,b;char v;cin>>v>>a;if(v=='+'){cin>>b;tof[a]=tof[b];tod[a]=tod[b];}else if(v=='-'){cin>>b;tof[a]=tof[b];tod[a]=(tod[b]^1); }else if(v=='T'){tof[a]=n+1;tod[a]=0;}else if(v=='F'){tof[a]=n+1;tod[a]=1; }else{tof[a]=n+2;tod[a]=0;}}init();for(int i=1;i<=n;i++){if(findf(i)==findf(tof[i])){if(tod[i]!=(d[i]^d[tof[i]]))ilg[findf(i)]=1;}combine(i,tof[i],tod[i]);}int ans=0;for(int i=1;i<=n;i++)if(findf(i)==n+2||ilg[findf(i)])ans++;cout<<ans<<'\n';
}
int main()
{//freopen("tribool.in","r",stdin);//freopen("tribool.out","w",stdout);ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int x,t;cin>>x>>t;while(t--)solve();return 0;
}