P9869 [NOIP2023] 三值逻辑 题解
题意
- 一种数据类型 tribool,有三种取值 T、F、U。给出一系列赋值操作,求出使得“赋值后的值与初值相等”的最少的“初值为 U 的变量个数”。
分析
- 首先,所有直接赋值成 U 的初值一定为 U;因为 T 和 F 之间的运算与 bool 相同,所以如果赋值为 T 或 F 出现矛盾的话也只能赋初值为 U。
- 用
来表示变量 的赋值来源是谁, 表示是否取反。也就是说,变量 的末值就是变量 的初值经过 运算后的值。可以发现,由于 的初值就是 的末值,所以可以让 先确定初(末)值,然后再直接赋给 。 - 把这个过程抽象成图,建立从
指向 的边,边权为 ,然后拓扑排序赋值即可。 - 可以发现如果从一个非环内的点出发,一定不会到达一个环,因为每个点的入度最多为
。 - 对于环,可以发现如果环内
的个数为奇数的话就是非法的,需要赋值为 U,否则赋为 T 或者 F 都可。挑一个环内的点赋值后将该点放入队列中(拆环)就可以继续拓排赋值。我使用的是 Floyd 判环。 - 一个避免特判的小技巧是把 T 设为第
个变量,U 设为第 个变量,统计答案时将第 个点排除即可。
考场代码
#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; }
感谢观看~有错请大佬们在评论区指出
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!