提示:
1. 此贪心题
详细题解代码后:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e5+1e3;
const int maxk = 25;
__inline int re() { int n; scanf("%d" , &n); return n; }
ll req[maxn][maxk];
ll has[maxn][maxk];
int res;
int n , s , k;
vector<int> g[maxn];
void range(int u , bool open = false)
{
for(int i=0;i<=k;i++) for(int j=i;j>=0 && (open || j>=i-1);j--)
{
ll cut = min(has[u][i] , req[u][j]);
has[u][i] -= cut;
req[u][j] -= cut;
if(!has[u][i]) break;
}
}
void dfs(int u , int fa)
{
for(int i=0;iint v = g[u][i];
if(v==fa) continue;
dfs(v, u);
for(int j=0;j< k;j++) req[u][j+1]+= req[v][j];
for(int j=1;j<=k;j++) has[u][j-1]+= has[v][j];
}
req[u][0]++;
ll urg = req[u][k] - has[u][k];
if(urg>0)
{
int many = (urg+s-1)/s;
has[u][k] += many*s;
res+= many;
}
range(u);
}
int main(int argc, char *argv[]) {
n = re(); s = re(); k = re();
for(int i=1;iint a = re() , b = re();
g[a].push_back(b);
g[b].push_back(a);
}
dfs(1, 0);
range(1 , true);
ll fin = 0;
for(int i=0;i<=k;i++) fin+= req[1][i];
res+= (fin+s-1)/s;
printf("%d\n" , res);
return 0;
}
每个结点保存到此结点距离为 0~k 的还可以覆盖和需要被覆盖的结点数。
如果当前结点距离为k 的可以覆盖的结点数少于需要被覆盖的结点数 , 说明这个结点不得不需要一些消防站。我们分配一些给它 , 最后我们把根结点需要建立的消防站计算出来就是答案啦。
每个结点两个量之间需要互相抵消 , 但并不是完全像最后计算根结点那样随意的抵消。 我们只用距离为i 的可以覆盖的结点数 去抵消 距离为i和i-1的需要被覆盖的结点数 , 详情见range.
为什么要这样做呢 , 如果我们去掉open 这个变量 , 那么抵消就是随意的 , 我们可能浪费了距离大的可以覆盖的结点配额。 我们只覆盖不得不覆盖的结点 , i 和 i-1 到这个结点父亲那里就已经不能被 i 覆盖了。
关于贪心的证明 , 有时候我们不能找到一种方法来证明这样做一定好 , 但是我们可以说明这样不比其他的决策更坏。 这个题就是如此 , 两个贪心的地方都可以这样说明 , 最后我们是正确的 , 只是因为我们的决策不太糟糕。