P9869 [NOIP2023] 三值逻辑 题解

题意

  • 一种数据类型 tribool,有三种取值 T、F、U。给出一系列赋值操作,求出使得“赋值后的值与初值相等”的最少的“初值为 U 的变量个数”。

分析

  • 首先,所有直接赋值成 U 的初值一定为 U;因为 T 和 F 之间的运算与 bool 相同,所以如果赋值为 T 或 F 出现矛盾的话也只能赋初值为 U。
  • fax 来表示变量 x 的赋值来源是谁,sgnx 表示是否取反。也就是说,变量 x 的末值就是变量 fax 的初值经过 sgnx 运算后的值。可以发现,由于 fax 的初值就是 fax 的末值,所以可以让 fax 先确定初(末)值,然后再直接赋给 x
  • 把这个过程抽象成图,建立从 fax 指向 x 的边,边权为 sgnx,然后拓扑排序赋值即可。
  • 可以发现如果从一个非环内的点出发,一定不会到达一个环,因为每个点的入度最多为 1
  • 对于环,可以发现如果环内 sgnx=1 的个数为奇数的话就是非法的,需要赋值为 U,否则赋为 T 或者 F 都可。挑一个环内的点赋值后将该点放入队列中(拆环)就可以继续拓排赋值。我使用的是 Floyd 判环。
  • 一个避免特判的小技巧是把 T 设为第 n+1 个变量,U 设为第 n+2 个变量,统计答案时将第 n+2 个点排除即可。

考场代码

#include <bits/stdc++.h>
#define int long long
#define N 100005
using namespace std;
int n, m, fa[N], sgn[N], num[N], du[N], ans;
int head[N], to[N], nxt[N], sg[N], tot;
bool vis[N];
queue<int> q;
inline void read(int &x) {
char ch = x = 0;
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + ch - 48;
ch = getchar();
}
return ;
}
inline void print(int x) {
static int stk[50];
int top = 0;
do {
stk[top++] = x % 10;
x /= 10;
} while (x);
while (top) {
putchar(stk[--top] + 48);
}
putchar('\n');
return ;
}
inline void add(int u, int v, int w) {
to[tot] = v;
sg[tot] = w;
nxt[tot] = head[u];
head[u] = tot++;
return ;
}
inline void color() { //拓扑排序,想不到名字就叫上色了
int x;
while (!q.empty()) {
x = q.front();
q.pop();
if (vis[x]) continue;
vis[x] = 1;
if (num[x] == 2 && x <= n) ans++; //统计答案,排除源点 U
if (num[x] == 0) { //赋初值
num[x] = 1;
}
for (int i = head[x]; ~i; i = nxt[i]) {
if (num[x] == 2) { //也是赋初值
num[to[i]] = 2;
} else {
num[to[i]] = num[x] * sg[i]; //其实这里乘不乘都无所谓,不过我还是模拟了
}
du[to[i]]--;
if (du[to[i]] == 0) q.push(to[i]);
}
}
return ;
}
void work(int x) {
int s = fa[fa[x]], t = fa[x], cnt = 0;
while (s != t) { //Floyd 判环
s = fa[fa[s]];
t = fa[t];
}
do { //统计取反个数
cnt += (sgn[s] == 1 ? 0 : 1);
s = fa[s];
} while (s != t);
if (cnt & 1) { //赋值拆环
num[s] = 2;
} else {
num[s] = 1;
}
du[s]--;
q.push(s);
color(); //拆了环后再次拓排
return ;
}
signed main() {
// freopen("tribool.in", "r", stdin);
// freopen("tribool.out", "w", stdout);
int c, T, x, y;
char ch;
read(c), read(T);
while (T--) {
read(n), read(m);
tot = ans = 0;
memset(num, 0, sizeof num);
memset(du, 0, sizeof du);
memset(vis, 0, sizeof vis);
memset(head, -1, sizeof head);
num[n + 1] = 1;
num[n + 2] = 2; //初始化两个源点
while (!q.empty()) q.pop();
for (int i = 1; i <= n; i++) {
fa[i] = i;
sgn[i] = 1; //每个值最初都指向自己,符号为正
}
for (int i = 1; i <= m; i++) {
ch = getchar();
while (ch != 'T' && ch != 'F' && ch != 'U' && ch != '+' && ch != '-') ch = getchar();
read(x);
if (ch == 'T') { //各种赋值操作
fa[x] = n + 1;
sgn[x] = 1;
} else if (ch == 'F') {
fa[x] = n + 1;
sgn[x] = -1;
} else if (ch == 'U') {
fa[x] = n + 2;
} else if (ch == '+') {
read(y);
fa[x] = fa[y]; //这里的赋值要指向 fa[y] 而不是 y,下同
sgn[x] = sgn[y];
} else {
read(y);
fa[x] = fa[y];
sgn[x] = -sgn[y];
}
}
for (int i = 1; i <= n; i++) { //建图
add(fa[i], i, sgn[i]);
du[i]++;
}
for (int i = 1; i <= n + 2; i++) { //拓排初始化
if (du[i] == 0) {
q.push(i);
}
}
color(); //首次拓排
for (int i = 1; i <= n; i++) {
if (!vis[i]) { //处理环的情况
work(i);
}
}
print(ans);
}
return 0;
}

感谢观看~有错请大佬们在评论区指出

posted @   HappyJaPhy  阅读(240)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示