题解:P9257 [PA 2022] Mędrcy

posted on 2025-01-02 14:19:01 | under | source

对于此类“智力游戏”问题,一种思路是每个人先根据已知条件推理,假如事实结果与推理相矛盾,则条件不成立。此题正是如此,每个贤者假设自己会所有咒语并加以推理,看看是否会矛盾。

\(S_i\)\(i\) 会的咒语集合,考虑一个人 \(x\) 在第 \(t\) 天离开的条件:

  1. \(t=1\)\(S_x=\varnothing\)。因为流浪者说至少有一条咒语,所以显然 \(x\) 会推出矛盾。
  2. \(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;
}
posted @ 2026-01-15 08:19  Zwi  阅读(1)  评论(0)    收藏  举报