大家好啊,我是唐诗
洛谷 P9869 [NOIP2023] 三值逻辑
首先看到这题,我们就可以拿 \(20\) 分的暴力。
然后对于仅有 \(T、F、U\) 的测试点,同一个位置取最后赋值的值即可,未赋值的一律是 T,这样就有有了 \(20\) 分。
考虑只有 \(U、+\) 的测试点,维护每个节点实际上最终被哪个节点赋值了(路径压缩),然后对于值被赋成同样位置的节点的值的点,它们的值都是一样的。也就是说,这其中一个集合,只要有一个被赋值为 U 那么其他的也绝对是 U,具体样子是一棵树,并查集维护即可,\(O(n)\),也是 \(20\) 分。
接下来,考虑正解。
首先需要知道,题目中要求我们的操作前与操作后值一致究竟意味着什么,不然,如果单纯这样维护,是困难的。由于每一个元素的值只会被改为最后一个修改它值的值,所以,我们只要将初值赋为最后一个修改它值的值即可,但是有一种情况,修改它的值实际上是由它自己决定的,会更随它的初值改变,这样的话就没办法了,只能让它为不确定。
于是我们考虑画出影响图,即对于形如 \(+、-\) 操作时,连一个从 \(y\) 到 \(x\) 的边,意味着它受 \(y\) 的影响。于是最后确定答案即为对于这样的森林,有多少节点被改为了自己值的非值或者被赋值为 \(U\) 的影响树的大小和。
对于 \(+\) 我们 \(+\) 很好操作,但对于 \(-\) 呢,毕竟需要把值非了,所以考虑建一个点表示非那个点,然后对于所有非点建一个非图。所以我们需要考虑一个点是否能通过边到达非它的值,如果可以,那这棵树就全不知道了(全是 \(U\))。
考虑赋值,我们只需要建三个虚点表示 \(T、F、U\) 即可,然后操作如上。
思路可能比较难懂,实际上我们是用反图上的点,表示 \(-\) 这种情况,然后反点不可能就没有被它赋值或被它赋值为它的反的情况,所以就在反图上也要正常连边,考虑所有情况。你实际情况下关系是有两种的:赋值为它的值,赋值为它的相反值。那么这两种操作,赋的值的来源点对于那个点值的来源点有两种情况:不变,反。所以对于目前点的连边,也是要考虑对于赋的值的来源点对于那个点值的来源点的正负关系。然后,有一些情况一个点是 \(U\)。
1.可以到达自己的反值点。
2.被赋值 \(U\)。
3.目前逻辑树内有 \(U\) 点。
处理即可,考虑反点和虚点的问题:对于反点,给目前点的负数,对于虚点 \(T=10^5+1,F=-10^5-1,U=0\),个人认为这样的处理是精妙的。
Code
//#include <bits/stdc++.h>
//using namespace std;
//const int N=5e5+5;
//int p[N],value[N],another[N],sz[N];
//bool dead[N],used[N];
//struct qry {
// char opt;
// int x,y;
//}qrys[N];
//int find(int x) {
// if(p[x]!=x) return p[x]=find(p[x]);
// return x;
//}
//int orzero(char x) {
// if(x=='T') return 1;
// else if(x=='F') return 2;
// else if(x=='U') return 3;
//}
//int n,m;
//signed main() {
// int c,tt;
// cin>>c>>tt;
// while(tt--) {
// cin>>n>>m;
// for(int i=1;i<=m;i++) {
// cin>>qrys[i].opt;
// if(qrys[i].opt=='+'||qrys[i].opt=='-') cin>>qrys[i].x>>qrys[i].y;
// else cin>>qrys[i].x;
// }
// reverse(qrys+1,qrys+1+n);
//// for(int i=1;i<=m;i++) {
//// if(qrys[i].opt=='+'||qrys[i].opt=='-') cout<<qrys[i].opt<<' '<<qrys[i].x<<' '<<qrys[i].y<<endl;
//// else cout<<qrys[i].opt<<' '<<qrys[i].x<<endl;
//// }
// for(int i=1;i<=n;i++)
// p[i]=i,used[i]=0,dead[i]=0,value[i]=0,another[i]=0,sz[i]=1;
// for(int i=1;i<=m;i++) {
// if(used[qrys[i].x]) continue;
// if(qrys[i].y!=qrys[i].x) used[qrys[i].x]=1;
// if(qrys[i].opt!='+'&&qrys[i].opt!='-')
// value[find(i)]=orzero(qrys[i].opt);
// else {
// if(qrys[i].opt=='+') {
// int fx=find(qrys[i].x),fy=find(qrys[i].y);
// if(value[fx]==0) {
// p[fx]=fy;
// sz[fy]+=sz[fx];
// }
// else if(value[fy]==0) {
// p[fy]=fx;
// sz[fx]+=sz[fy];
// }
// else if(value[fx]==value[fy]) {
// p[fx]=fy;
// sz[fy]+=sz[fx];
// }
// else {
//
// }
// }
// else {
// int fx=find(qrys[i].x),fy=find(qrys[i].y);
//
// }
// }
// }
// memset(used,0,sizeof used);
// int ans=0;
// for(int i=1;i<=m;i++) {
// if(used[find(i)]&&value[find(i)]==orzero('U')) {
// ans+=sz[find(i)];
// used[find(i)]=1;
// }
// }
// cout<<ans<<endl;
// }
// return 0;
//}
//我用草饲养你的马
//明明想的关键做法对了啊!
#include <bits/stdc++.h>
#define int long long
#define T 100001
#define F -100001
#define U 0
using namespace std;
const int N=5e5+5;
unordered_map<int,int> p;
unordered_map<int,bool> vis;
int n,m,tt,c,x,y;
int ans=0;
int find(int x) {
if(p[x]!=x) return p[x]=find(p[x]);
return x;
}
int check(int x) {
//cout<<"?"<<x<<' '<<vis[-x]<<endl;
if(x==T||x==F) return x;
if(x==U||vis[-x]) {
//cout<<"**"<<endl;
return U;
}
int go;
if(vis[x]) return x;
if(x==p[x]) return x;
else {
vis[x]=1;
go=p[x]=check(p[x]);
//cout<<x<<' '<<p[x]<<"(("<<endl;
vis[x]=0;
}
return go;
}
signed main() {
cin>>c>>tt;
while(tt--) {
cin>>n>>m;
for(int i=1;i<=n;i++) p[i]=i,p[-i]=-i;
for(int i=1;i<=m;i++) {
char opt;
cin>>opt;
if(opt=='+'||opt=='-') {
cin>>x>>y;
if(opt=='+') p[x]=p[y],p[-x]=p[-y];
else p[x]=p[-y],p[-x]=p[y];
}
else {
cin>>x;
if(opt=='T') p[x]=T,p[-x]=F;
else if(opt=='F') p[x]=F,p[-x]=T;
else p[x]=U,p[-x]=U;
}
}
ans=0;
for(int i=1;i<=n;i++) {
//cout<<"!!"<<i<<endl;
if(check(i)==U) ans++;
}
cout<<ans<<endl;
}
return 0;
}

浙公网安备 33010602011771号