*题解:P3557 [POI 2013] GRA-Tower Defense Game

题目链接

解析

难点在于读懂 \(k\) 是未升级时可行的塔楼个数,即每个塔楼只覆盖与其距离小于等于 \(1\) 的点。

我们称未升级塔楼为 \(A\) 类点,升级后的塔楼为 \(B\) 类点。假设我们已经有了这样一个用 \(k\)\(A\) 类点覆盖整张图的方案,那么通过手玩不难发现对于任意一个图上的点,如果令其为 \(B\) 类点,则其必定能覆盖一个原图中的 \(A\) 类点及该点覆盖的点。

于是就可以想到一种策略:每次任意找一个未被覆盖的点,令其为 \(B\) 类点,重复直至所有点都被覆盖。由于只用 \(A\) 类点覆盖的方案只会用小于等于 \(k\) 个点,而通过这种策略每次都至少能覆盖一个未被完全覆盖的 \(A\) 类点,所以利用这种策略只会构造出不超过 \(k\) 个点。其中“完全覆盖”的定义是覆盖与该点距离不超过 \(1\) 的点。

对于实现,直接枚举所有点,没被覆盖就令其为 \(B\) 类点,代码是这样的:

vector<int> res;
for(int i=1;i<=n;i++)if(!vis[i]){
  vis[i] = true;
  for(int j = head[i];j;j = nxt[j]){
    vis[to[j]] = true;
    for(int k = head[to[j]];k;k = nxt[k]){
      vis[to[k]] = true;
    }
  }
  res.push_back(i);
}

虽然有嵌套,但仍然是 \(O(m)\) 的。我们来分析一下令某个点为 \(B\) 类点后发生了什么。对于 \(i\),与其距离相差小于等于 \(2\) 的点都被覆盖了;对于 \(to_j\),与其距离相差小于等于 \(1\) 的点都被覆盖了;\(to_k\) 也被覆盖了。关键在于 \(to_j\)。与其相邻的点都被覆盖使得它们不会作为 \(i\) 出现,这意味着同一个点至多作为 \(to_j\) 一次出现并进行枚举边的操作。故时间复杂度 \(O(m)\)

代码

#include <bits/stdc++.h>
#define eps 0.0000000001
#define ls(x) ((x) << 1)
#define rs(x) (((x) << 1) | 1)
#define mid ((l + r) >> 1)
using namespace std;
typedef long long ll;
typedef unsigned ui;
typedef pair<int,int> pii;
const int N = 5e5 + 5,M = 2e6 + 5,P = 2000000,mod = 998244353,mod2 = 1e9 + 7,b1 = 131,b2 = 13331; 
int head[N],to[M],nxt[M],cnt,dep[N];
bool cov[N],vis[N];
void add(int a,int b){
	cnt++;
	to[cnt] = b;
	nxt[cnt] = head[a];
	head[a] = cnt;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n,m,k;
	cin>>n>>m>>k;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		add(u,v);
		add(v,u);
	}
	vector<int> res;
	for(int i=1;i<=n;i++)if(!vis[i]){
		vis[i] = true;
		for(int j = head[i];j;j = nxt[j]){
			vis[to[j]] = true;
			for(int k = head[to[j]];k;k = nxt[k]){
				vis[to[k]] = true;
			}
		}
		res.push_back(i);
	}
	cout<<res.size()<<'\n';
	for(int i : res){
		cout<<i<<" ";
	}
	return 0;
}
posted @ 2026-07-01 11:20  yutar  阅读(7)  评论(0)    收藏  举报