noip模拟赛 兔子
【问题描述】
在一片草原上有N个兔子窝,每个窝里住着一只兔子,有M条路径连接这些窝。更特殊地是,至多只有一个兔子窝有3条或更多的路径与它相连,其它的兔子窝只有1条或2条路径与其相连。换句话讲,这些兔子窝之前的路径构成一张N个点、M条边的无向连通图,而度数大于2的点至多有1个。
兔子们决定把其中K个兔子窝扩建成临时避难所。当危险来临时,每只兔子均会同时前往距离它最近的避难所躲避,路程中花费的时间在数值上等于经过的路径条数。为了在最短的时间内让所有兔子脱离危险,请你安排一种建造避难所的方式,使最后一只到达避难所的兔子所花费的时间尽量少。
【输入】
第一行有3个整数N,M,K,分别表示兔子窝的个数、路径数、计划建造的避难所数。
接下来M行每行三个整数x,y,表示第x个兔子窝和第y个兔子窝之间有一条路径相连。任意两个兔子窝之间至多只有1条路径。
【输出】
一个整数,表示最后一只到达避难所的兔子花费的最短时间。
【输入输出样例1】
rabbit.in
5 5 2
1 2
2 3
1 4
1 5
4 5
rabbit.out
1
【输入输出样例1说明】
在第2个和第5个兔子窝建造避难所,这样其它兔子窝的兔子最多只需要经过1条路径就可以到达某个避难所。
【数据规模与约定】
对于30%的数据,N≤15,K≤4;
对于60%的数据,N≤100;
对于100%的数据,1≤K≤N≤1,000,1≤M≤1,500
分析:很明显可以看出这道题要二分,最难的就是要怎么check?假设当前二分的答案是x,我一开始的思路是对于第i个点,将所有与它距离<=x的点打个标记,最后看看能不能有k个点上有所有点的标记.这样就要状压,不好做.换个思路,每个点能覆盖到的点数为2*x+1,处理一下把所有点覆盖需要的点数是不是<=k.可是直接这样处理也非常难,题目中还有一个条件没用上:只有一个度数≥3的点,其它的点的度数≤2,注意≤2这个条件,这说明了一个重要的信息:原图是有一些链和环构成的,都连在一个中心上.
如果没有环,只有链,就很好做了,设当前链的长度为cnt,那么覆盖这条链所需要的点数位cnt / (2*x + 1)上取整.这样我们在距中心点不超过x的一个点上建造一个避难所,把它覆盖到的点给删掉,这时一定会删掉根节点,那么剩下的就成了若干条链,就很好做了.为什么一定要覆盖中心呢?中心其实就相当于一个“中转站”,覆盖的时候经过这个中转站,剩下的距离每一步都能覆盖多个点,如果不经过中心点的话,每一步就只能覆盖一个点,而且这和之前的二分想法没区别,很难处理.
二分想不到如何check的时候,把题目中给的所有限制和人为的限制给列出来,如果不满足某种限制的情况很好求,就针对这个限制进行check,例如本题中的k个避难所.
#include <queue> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 3010; int n, m, k, head[maxn], to[maxn], nextt[maxn], tot = 1, du[maxn], s; int d[maxn], vis[maxn], ans, cnt; queue <int> q; void add(int x, int y) { to[tot] = y; nextt[tot] = head[x]; head[x] = tot++; } void bfs() { q.push(s); vis[s] = 1; d[s] = 0; while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; i; i = nextt[i]) { int v = to[i]; if (!vis[v]) { vis[v] = 1; d[v] = d[u] + 1; q.push(v); } } } } void dfs(int x, int rest) { vis[x] = 1; if (!rest) return; for (int i = head[x]; i; i = nextt[i]) { int v = to[i]; if (!vis[v]) dfs(v, rest - 1); } } void dfs2(int x) { cnt++; vis[x] = 1; for (int i = head[x]; i; i = nextt[i]) { int v = to[i]; if (!vis[v]) dfs2(v); } } bool check(int x) { for (int i = 1; i <= n; i++) { if (d[i] <= x) { memset(vis, 0, sizeof(vis)); dfs(i, x); int temp = 1; for (int j = 1; j <= n; j++) { if (!vis[j]) { cnt = 0; dfs2(j); temp += cnt / (x * 2 + 1); if (cnt % (x * 2 + 1)) temp++; } } if (temp <= k) return true; } } return false; } int main() { scanf("%d%d%d", &n, &m, &k); for (int i = 1; i <= m; i++) { int x, y; scanf("%d%d", &x, &y); du[x]++; du[y]++; add(x, y); add(y, x); } s = 1; for (int i = 1; i <= n; i++) if (du[i] >= 3) { s = i; break; } bfs(); int l = 0, r = n; while (l <= r) { int mid = (l + r) >> 1; if (check(mid)) { ans = mid; r = mid - 1; } else l = mid + 1; } printf("%d\n", ans); return 0; }