P9869 [NOIP2023] 三值逻辑 题解
NOIP2023 T2 三值逻辑 题解
思路
乍一看好像很并查集,而且不太难,但是,
注意到:按顺序运行这 \(m\) 条语句
事情并没有那么简单。
比如说如下情况:
x1:=T
x2:=x1
x1:=F
这时,\(x_2\)就不能简单地指向\(x_1\),否则当\(x_1\)被修改时,\(x_2\)也会被修改。
那么如何解决这个问题呢?
其实有点可持久化的感觉。我们可以对于每次修改\(x_1\)时,为它新建一个版本。以后所有形如\(x_2:=x_1\)的赋值操作,\(x_2\)都指向最新的版本。可以以边的形式存储它们的对应关系,\(x_2:=x_1\)则由\(x_2\)向\(x_1\)指一条边权为0的边(下文称为正边),\(x_2:=-x_1\)则由\(x_2\)向\(x_1\)指一条边权为1的边(下文称为负边)(这些边权是干什么的?这是一会儿要用到的神奇妙妙工具)。最终的产物像若干个栈。每个位置都有一个栈,每个栈上都摞着若干个节点。
像这样。有些东西没画,各位自行脑补。
可以观察到它是一个森林,森林中每棵树上的节点的值都相等。
题干中说,要让执行了所有语句后所有变量的最终值和初始值相等。那么我们可以从每个栈的栈底向栈顶指一条正边。于是我们得到了若干个环。最终的图也就是若干基环树,但是本文中的做法并没有用到什么基环树,笔者也不会基环树。
我们要找到只能初始化为\(U\)的位置,那么什么时候一个位置只能被初始化为\(U\)呢?
第一种情况,它在最后一次赋值(“栈顶”)中被直接或间接赋值为\(U\)。这是显然的。
第二种情况,它的“栈底”(或“栈顶”)处在一个环里,且这个环中有奇数条负边。这也是显然的。
显然我们要集中精力求第二种。
依次对每一个位置进行DFS,如果DFS到被明确地赋值了的节点(形如\(x_1:=T\)),那么回溯时返回这个值,赋给回到的节点。否则返回0(也可以是-1或任何你喜欢的数。如果你也设为0,那么记得将数组初始化为-1)。
如果DFS到了一个环,且当前经过的负边个数减去上一次到这个点时的负边个数是一个奇数,那么赋值为\(U\)。可以在DFS时以参数形式传递当前经过的负边个数,用数组存储上一次到某位置时的负边个数。这时我们的负边权值为1就派上了用场,路径的权值和即是负边个数。
于是此题得解。
注意,当出现自环时,不能简单粗暴地忽略或认为无解,这样会挂40分(别问我怎么知道的);而是把它也加进图里。因为它在栈中相当于上下两节点之间的连边,是可以存在的。此时注意赋值顺序,不要像笔者一样先把节点入栈再从该节点向栈顶连边。
实现
原谅我抽象的码风
#include <iostream>
#include <cstring>
#define N 300005
int to[N],top[N],wt[N];
bool vis[N];
int a[N];
int tlen[N];
int dfs(int x,int len)
{
if(a[x]>=0)
return a[x];
if(vis[x])
{
if((len-tlen[x])&1)
return a[x]=3;
return a[x]=0;
}
vis[x]=1;
tlen[x]=len;
if(!to[x])
return a[x]=0;
int tmp=dfs(to[x],len+wt[x]);
if(tmp==3||tmp==0)
a[x]=tmp;
else
{
if(wt[x])
a[x]=3-tmp;
else
a[x]=tmp;
}
vis[x]=0;
return a[x];
}
int main()
{
int c,t;
scanf("%d%d",&c,&t);
while(t--)
{
int n,m;
scanf("%d%d",&n,&m);
memset(top,0,sizeof(top));
memset(to,0,sizeof(to));
memset(a,-1,sizeof(a));
memset(vis,0,sizeof(vis));
memset(tlen,0,sizeof(tlen));
int cnt=0;
for(int i=1;i<=n;i++)
top[i]=++cnt;
for(int i=1;i<=m;i++)
{
char opt;
std::cin>>opt;
if(opt=='+')
{
int u,v;
scanf("%d%d",&u,&v);
cnt++;
to[cnt]=top[v];
top[u]=cnt;
wt[cnt]=0;
}
if(opt=='-')
{
int u,v;
scanf("%d%d",&u,&v);
cnt++;
to[cnt]=top[v];
top[u]=cnt;
wt[cnt]=1;
}
if(opt=='T'||opt=='F'||opt=='U')
{
int v;
scanf("%d",&v);
top[v]=++cnt;
if(opt=='T')
a[cnt]=1;
else if(opt=='F')
a[cnt]=2;
else
a[cnt]=3;
}
}
for(int i=1;i<=n;i++)
if(top[i]!=i)
to[i]=top[i],wt[i]=0;
for(int i=1;i<=n;i++)
if(a[i]<0)
dfs(i,0);
int ans=0;
for(int i=1;i<=n;i++)
if(a[i]==3)
ans++;
printf("%d\n",ans);
}
}