*题解: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;
}

浙公网安备 33010602011771号