[建图] [2SAT] [并查集] 题解:AT_agc059_c [AGC059C] Guessing Permutation for as Long as Possible
posted on 2025-03-18 03:44:47 | under | source
题意:有排列 \(p_1\dots p_n\),接下来提出 \(\frac {n(n-1)}2\) 次询问,每次 \((A_i,B_i)\) 即可得知 \(p_{A_i},p_{B_i}\) 的大小关系。已知每次询问前并不知道它俩的大小关系,求满足条件的排列个数。\(n\le 4\times 10^2\)。
先判定。对排列建一张竞赛图,边 \(a\to b\) 表示 \(a<b\),那么某个时刻得知 \(u,v\) 大小关系,当前仅当存在两点间路径。
注意到该图满足传递性,那么只需考虑两条边构成的路径即可。对于较大的路径一定存在简化的两条边的路径。
因此对于询问 \((x,y)\),设存在 \((x,i),(i,y)\) 在此前询问,那么必须满足不能存在 \(p_x<p_i<p_y\) 或 \(p_x>p_i>p_y\)。
将大小关系视为点,用点的黑白描述大于还是小于。对于边 \((u,v,w)\),若 \(w=1\) 则 \(u,v\) 大小关系相反反之相同。
不难发现一种合法的染色方案一定唯一存在一种排列与其对应。易证唯一性。对于存在性,假设不存在,意味着出现了环,但是在题目条件下环一定非法。因为考虑环上询问时间最晚的边,那么询问它时已经有路径存在了。
然后变成 2-SAT 问题求方案数,由于限制双向,所以答案即为 \(2\) 的连通块个数次幂。无解即为存在异或起来为 \(1\) 的环,用带权并查集维护即可。\(O(n^3)\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 4e2 + 5, M = 1e5 + 5, mod = 1e9 + 7;
int n, x, y, id[N][N], idd, ans, fa[M], faw[M];
bool vis[M], fl;
inline int find(int u){
if(fa[u] == u) return u;
int x = find(fa[u]); faw[u] ^= faw[fa[u]];
return fa[u] = x;
}
inline void add(int x, int y, int w){
int fx = find(x), fy = find(y);
if(fx == fy){
if(faw[x] ^ faw[y] ^ w) fl = true;
return ;
}
fa[fx] = fy, faw[fx] = w ^ faw[x] ^ faw[y];
}
signed main(){
cin >> n;
for(int i = 1; i < n; ++i)
for(int j = i + 1; j <= n; ++j) id[i][j] = id[j][i] = ++idd;
for(int i = 1; i <= idd; ++i) fa[i] = i;
for(int i = 1; i <= n * (n - 1) / 2; ++i){
scanf("%d%d", &x, &y);
for(int j = 1; j <= n; ++j){
if(j == x || j == y) continue;
int a = id[x][j], b = id[j][y];
if(vis[a] && vis[b]) add(a, b, (x < j && j < y));
}
vis[id[x][y]] = true;
}
if(fl) {puts("0"); return 0;}
ans = 1;
for(int i = 1; i <= idd; ++i) if(fa[i] == i) ans = 2ll * ans % mod;
cout << ans;
return 0;
}

浙公网安备 33010602011771号