路由器
题目描述
Farmer John 最近买了些新电脑,它向为奶牛们提供上网的机会,但是上网需要路由器,FJ想尽量少买路由器。FJ 有N个(编号为1..N)个农场,由 N-1 条长度是1的边连接起来,没有环. FJ 可以决定把路由器放在哪些农场. 每个农场只有长度为 K的网线,该农场可以连接到距离小于等于K的路由器. 路由器本身已经可以连接到互连网,可以被放置到任意的农场.
FJ至少要买多少个路由器,才能让每个农场都能上网?农场想要上网的话,至少要连接到一个包含路由器的农场.
多组询问
输入格式
第1行:一个整数T,表示数据组数
第2行:两个整数: N 和 K
第 3..N行: 每行两个整数ai,bi,表示农场ai与bi之间有长度为1的边.
输出格式
共T行:每行一个整数,表示FJ至少需要买多少个路由器.
输入样例
1
8 2
3 6
7 1
1 8
2 4
1 4
4 5
2 6
输出样例
2
样例解释
在1号点和2号点安装路由器
数据范围
\(30\%:n<=100\)
另外\(10\%:\)保证数据为一条链
\(100\%:n<=100000,k<=10,T<=10\)
保证数据纯随机
Solution
30分:枚举
40 分: 起启示正解的作用 , 我们发现一条链就是一段区间 , 其中的连接情况已经不重要了 , 我们可以从右端点一直往左走 , 每碰到一个没有覆盖的点 , 就在它左方 k 个点放一个路由器 , 然后从这个路由器开始一直向左走 k 个距离 , 都能被覆盖 , 那么其实就是看 n 是否能整除 (2 × k + 1) , 能输出 n/(2 × k + 1) , 否则将答案 +1
100 分:
方法一 :
我们发现,每个农场都要上网,这是解题的关键。我们先搞出一棵树,然后从深度最深的做起。因为最深的也要上网,而且它无法通过其后代的路由器来上网,必然是通过其上方的节点来上网。
经观察发现,必然是在其祖先上放最好,而且放在能放的最远处最好。因为当前点已经最深,与其共享同一个祖先的点也必然能上网。而越远,放置路由器的上方的点就有更大可能上网。于是我们简单证明了贪心的正确性。
于是我们按深度排序,将最深的当前不能上网的点向上跳至最远的能使其上网的点,然后将网络信号扩展,将能被影响到的点打上标记。然后继续重复此操作,指导所有点都能上网了为止,同时统计答案。
这个的时间复杂度呢,如果排序用桶排,仍旧无法保证是线性的,理论上最坏是平方级别的 ( 不可能达到 ) ,所以这就有点玄学了。因为每次打标记后的点还是可能放路由器,很多点会被重复搜到并重复打标记,所以这不是严格的线性了。但实测是能过的,下面有一种更加稳定可靠的方法。
方法二 :
直接一遍 \(dfs\) ,并在 \(dfs\) 过程中搞贪心。我们记录两个数组 \(dis[]\) 和 \(near[].\)
\(dis[]\) 代表离当前点 \(root\) 最远的不能上网的后代 ( 包括自己 ) 离当前点的距离是多少 . 一开始\(dis[root] = 0 ;\)
\(near[]\) 代表离当前点 \(root\) 最近的安装了路由器的后代 ( 包括自己 ) 离当前点 \(root\) 的距离是多少 .
一开始 \(near[root] = inf .\) 我们发现这两个记录的信息很有意思,因为我们知道了最有用的路由器的位置和最需要被 “ 拯救 ” 的节点的位置,由于全部要能上网,所以必须要拯救那个节点。此时就可以贪心了。
\(dfs\) 完后代后,先更新出 \(near, dis\) 的值:
\(near[root] = min(near[root], near[v] + 1);\)
\(dis[root] = max(dis[root], dis[v] + 1);\)
然后如果 \(dis[root] + near[root] <= K\) ,代表不用在此点装路由器就可以使其后代
( 包括自己 ) 全部上网,那么 \(dis[root] = −1 .\)
如果 \(dis[root] == K\) ,代表前面更新不了,而且安装路由器 “ 迫在眉睫 ” ,于是答案加1 , \(dis[root] = −1, near[root] = 0, ans + +.\)
每个节点都保证其后代都能上网,更新记录的信息,是不是还有点 dp 的意味呢 ?
最后由于无法保证根节点 1 能上网,要特判一下 ( 就是说根离最远的上不了网的点距离不足 K ,不用急迫的安装,但是往上已经没路了,而自己的一些后代和可怜的自己还无法上网 ) ,这始这需要判断一下 \(dis[1]\) 是否等于 -1 就行了。
Code
#include
#define in(i) (i=read())
using namespace std;
const int N=1e5+10,inf=2e9;
int read() {
int ans=0,f=1; char i=getchar();
while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
while(i>='0' && i<='9') ans=(ans<<1)+(ans<<3)+i-'0',i=getchar();
return ans*=f;
}
int T,n,k,cur,ans;
int to[N<<1],nex[N<<1],head[N];
int dis[N],near[N];
void add(int a,int b) {
to[++cur]=b,nex[cur]=head[a],head[a]=cur;
to[++cur]=a,nex[cur]=head[b],head[b]=cur;
}
void dfs(int u,int fa) {
dis[u]=0,near[u]=inf;
for(int i=head[u];i;i=nex[i]) {
if(to[i]==fa) continue;
dfs(to[i],u);
dis[u]=max(dis[u],dis[to[i]]+1);
near[u]=min(near[u],near[to[i]]+1);
}
if(dis[u]+near[u]<=k) dis[u]=-1;
if(dis[u]==k) dis[u]=-1,near[u]=0,ans++;
}
int main() {
freopen("router.in","r",stdin);
freopen("router.out","w",stdout);
in(T);
while(T--) {
memset(head,0,sizeof(head)); cur=ans=0;
in(n),in(k);
for(int i=1,a,b;i