原文链接https://www.cnblogs.com/zhouzhendong/p/BZOJ1117.html
题目传送门 - BZOJ1117
题意
给你一棵树,现在要建立一些消防站,有以下要求:
1. 消防站要建立在节点上,每个节点可能建立不只一个消防站。
2. 每个节点应该被一个消防站管理,这个消防站不一定建立在该节点上。
3. 每个消防站可以管理至多s个节点。
4. 消防站只能管理距离(两点间最短路径的边数)不超过k的结点。
请问至少要设立多少个消防站。
题解
考虑贪心从下往上走。
设 rem[x][i] 表示 x 的子树中与 x 的距离为 i 的未决策节点总数。
设 foc[x][i] 表示 x 的子树中与 x 的距离为 i 的灭火器还能管辖多少节点。
贪心策略就是尽量把灭火器往祖先上应用。
分两个情况再描述一下:
1. 对于 rem[x][k] 这一部分,我们需要在节点 x 新增灭火器。
2. i 从小到大检验 foc[x][i] ,如果它可以灭 rem[x][i] 或者 rem[x][i-1] ,那么必然灭了最好,否则到上面了就灭不了了;就算别的灭火器灭了它,我们只需要交换这两个灭火器的消灭对象就可以打到至少不劣的效果。
代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N=100005,K=25; int read(){ int x=0; char ch=getchar(); while (!isdigit(ch)) ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return x; } struct Graph{ static const int M=N*2; int cnt,y[M],nxt[M],fst[N]; void clear(){ cnt=1; memset(fst,0,sizeof fst); } void add(int a,int b){ y[++cnt]=b,nxt[cnt]=fst[a],fst[a]=cnt; } }g; int n,s,k; LL rem[N][K],foc[N][K]; int ans=0; void solve(int x,int pre){ rem[x][0]++; for (int i=g.fst[x];i;i=g.nxt[i]) if (g.y[i]!=pre){ int y=g.y[i]; solve(y,x); for (int j=0;j<k;j++){ rem[x][j+1]+=rem[y][j]; foc[x][j]+=foc[y][j+1]; } } int d=(rem[x][k]+s-1)/s; ans+=d; foc[x][k]+=d*s; for (int i=0;i<=k;i++) for (int j=i;foc[x][i]&&j>=0&&(j>=i-1||x==1);j--){ d=min(foc[x][i],rem[x][j]); foc[x][i]-=d; rem[x][j]-=d; } } int main(){ n=read(),s=read(),k=read(); g.clear(); for (int i=1;i<n;i++){ int a=read(),b=read(); g.add(a,b); g.add(b,a); } solve(1,0); int t=0; for (int i=0;i<=k;i++) t+=rem[1][i]; printf("%d ",ans+(t+s-1)/s); return 0; }