题解:P9257 [PA 2022] Mędrcy
posted on 2025-01-02 14:19:01 | under | source
对于此类“智力游戏”问题,一种思路是每个人先根据已知条件推理,假如事实结果与推理相矛盾,则条件不成立。此题正是如此,每个贤者假设自己会所有咒语并加以推理,看看是否会矛盾。
记 \(S_i\) 为 \(i\) 会的咒语集合,考虑一个人 \(x\) 在第 \(t\) 天离开的条件:
- \(t=1\):\(S_x=\varnothing\)。因为流浪者说至少有一条咒语,所以显然 \(x\) 会推出矛盾。
- \(t>1\):每个人可以转换立场,站在别人的角度推理第 \(t-1\) 天,但是 \(x\) 会认为 \(S_y=S_x\cap S_y\),因为他只能得知谁和他同时会一个咒语。所以条件就是存在一个 \(y\),满足带入对于 \(x\) 而言的 \(S\) 并按照第 \(t-1\) 天的条件判断,\(y\) 应该离开,但他却没有离开,那么 \(x\) 就会在第 \(t\) 天离开。
发现不好刻画 \(y\) 没有离开这一条件,但是本题只需求出第一次有人离开的时刻即可,所以不妨设第 \(t\) 天前没有任何人离开,不影响正确性。那么归纳得第 \(t\) 天有人离开的条件是:存在互不相同的 \(a_1\dots a_t\),满足 \(S_{a_1}\cap S_{a_2}\dots \cap S_{a_t}=\varnothing\)。
又因为每条咒语恰有 \(2\) 人不会,所以转化为补集 \(T\) 的条件:\(T_{a_1}\cup T_{a_2}\dots \cup T_{a_t}=A\)。\(A\) 为全集。
咒语为边、人为点,那么就是求最小点覆盖,离开者集合即为最小点覆盖的并。一般图上这是 NPC 问题,但是注意到 \(k\) 很小,所以大力搜索即可。
需要剪枝:先考虑度数最大的点,若其度 \(>2\) 暴力递归。否则所有点度数 \(\le 2\) 则为若干链和环,可以直接处理。那么最坏情况下度数为 \(3\),复杂度 \(T_k=T_{k-1}+T_{k-3}+O(n)\),经计算可以通过。
代码
比较恶心,但完全不卡常,平均 4ms 过。假如你遇到了 TLE 或 MLE,务必检查是否陷入死循环。
#include<bits/stdc++.h>
using namespace std;
const int N = 3e3 + 5;
int T, n, m, k, mi, u[N], v[N], q[N], qn, q2[N], qn2, du[N], col[N], e[N][N], stk[N];
bool vis[N], ans[N], vis2[N];
vector<int> to[N], buc;
inline void debug(){
for(int i = 1; i <= n; ++i) cout << vis[i] << ' ';
cout << endl;
for(int i = 1; i <= n; ++i) cout << du[i] << ' ';
cout << endl;
cout << endl;
}
inline void init(){
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= n; ++i){
to[i].clear(), ans[i] = du[i] = 0;
for(int j = 1; j <= n; ++j) e[i][j] = false;
}
for(int i = 1; i <= m; ++i){
scanf("%d%d", &u[i], &v[i]);
if(!e[u[i]][v[i]]){
to[u[i]].push_back(v[i]), to[v[i]].push_back(u[i]);
++du[u[i]], ++du[v[i]];
e[u[i]][v[i]] = e[v[i]][u[i]] = true;
}
}
}
inline void dfs2(int u, int from, bool &iscir){
buc.push_back(u);
for(auto v : to[u]){
if(v == from || !du[v] || vis[v]) continue;
if(!col[v]) col[v] = 3 - col[u], dfs2(v, u, iscir);
else iscir = true;
}
}
inline void dfs(int cnt){
if(cnt > mi) return ;
int now = 0;
for(int i = 1; i <= n; ++i) if(!vis[i]) now = (du[i] > du[now] ? i : now);
if(!now){
if(cnt < mi){
for(int i = 1; i <= n; ++i) ans[i] = 0;
mi = cnt;
}
for(int i = 1; i <= n; ++i) ans[i] |= vis[i];
return ;
}
if(du[now] > 2){
vis[now] = true;
for(auto i : to[now]) if(!vis[i]) --du[i], --du[now];
dfs(cnt + 1);
vis[now] = false;
for(auto i : to[now]) if(!vis[i]) ++du[i], ++du[now];
vector<int> oth;
for(auto i : to[now])
if(!vis[i]){
oth.push_back(i), vis[i] = true;
for(auto j : to[i]) if(!vis[j]) --du[i], --du[j];
}
dfs(cnt + oth.size());
while(!oth.empty()){
int u = oth.back(); oth.pop_back();
vis[u] = false;
for(auto v : to[u]) if(!vis[v]) ++du[u], ++du[v];
}
}
else{
for(int i = 1; i <= n; ++i) col[i] = 0, vis2[i] = vis[i];
for(int i = 1; i <= n; ++i)
if(!vis[i] && du[i] && !col[i]){
bool iscir = false;
col[i] = 1, dfs2(i, 0, iscir);
if(iscir){
cnt += (buc.size() + 1) / 2;
while(!buc.empty()) vis[buc.back()] = true, buc.pop_back();
}
else{
int c1 = 0, c2 = 0, pp;
for(auto i : buc) c1 += col[i] == 1, c2 += col[i] == 2;
cnt += min(c1, c2);
while(!buc.empty()){
pp = buc.back();
if(c1 == c2) vis[pp] = true;
if(c1 < c2 && col[pp] == 1) vis[pp] = true;
if(c1 > c2 && col[pp] == 2) vis[pp] = true;
buc.pop_back();
}
}
}
if(cnt < mi){
for(int i = 1; i <= n; ++i) ans[i] = false;
mi = cnt;
}
if(cnt == mi) for(int i = 1; i <= n; ++i) ans[i] |= vis[i];
for(int i = 1; i <= n; ++i) vis[i] = vis2[i];
return ;
}
}
inline void solve(){
mi = k + 1;
dfs(0);
if(mi > k) puts("-1");
else{
vector<int> Ans;
for(int i = 1; i <= n; ++i) if(ans[i]) Ans.push_back(i);
printf("%d %d\n", mi, Ans.size());
for(auto i : Ans) printf("%d ", i);
putchar('\n');
}
}
signed main(){
cin >> T;
while(T--) init(), solve();
return 0;
}

浙公网安备 33010602011771号