By 佚名上传
一道我想不到的树dp。。。
题干给出的ci是一个传递只与边有关的东西。。
也就是说,显然,一个点被激发的先后,只影响其周围的点,然后再考虑传递之类
先将无根树转换成有根数,那么每条边就看成连接y与其父亲x,状态就在这上面设
令fi:先激发i的父亲再激发i,这时激发整个i的子树需要的最少花费
令gi:先激发i再激发i的父亲,这时激发整个i的子树需要的最少花费
对于任意i,不妨设最优方案里i的子树中,
t∈{选择先激发i} k∈{选择先激发自己}
那么fi = ∑ft + ∑gk + di - cfa - ∑ck
gi = ∑ft + ∑gk + di - ∑ck
从递推式可以看到,对于任意的i,总有fi <= gi
并且递推式中的di - cfa - ∑ck 以及 di - ∑ck 一定是非负的
对于typA:
对于任意点i,显然fi与gi的差值只能是0或1,考虑i的父亲x如何在fi与gi中选择
那么如果fi == gi,选gi一定更优,因为这样对于答案至少作出了c[i]的贡献(在允许的情况下贡献给父亲)
如果gi - fi == 1,取fi一定更优,因为如果选择gi,gi中多出的1能够被抵消,一定得是父亲有得扣才行(如果儿子们贡献太多,父亲的激发根本不需花费),反之如果选择fi,那么父亲稳定给儿子1点能量,也就能对答案贡献-1了
基于以上两个贪心搞出所有的fi,gi即可
对于typB:
此时fi与gi的差值就属于[0,5]了。。
如果过多地选择fi,那么激活x可能就开销很大
如果过多地选择gi,激活各个儿子开销大不说,x还不一定能享受到它们的贡献(因为x需要的能量有上界)
这样就有些类似一个背包
定义Fi,j:对于前i个子孙,给x贡献了j点能量,需要的最小开销
和01背包一样地递推
利用F数组求出所有的fi,gi
然而第一发代码F数组边界判错了。。GG
综上。。。此题不单单是树dp。。。还考察贪心。。GG。。。
总结自网上某神犇的题解
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1E5 + 10;
typedef long long LL;
const LL INF = ~0U>>1;
int n,d[maxn],c[maxn];
LL f[maxn],g[maxn],F[maxn];
bool flag;
vector v[maxn];
void Dfs(int x,int fa)
{
int siz,tc;
siz = tc = 0;
for (int i = 0; i < v[x].size(); i++) {
int to = v[x][i];
if (to == fa) continue;
++siz;
tc += c[to];
Dfs(to,x);
}
if (flag) {
for (int i = 1; i <= tc; i++) F[i] = INF;
F[0] = 0;
for (int i = 0; i < v[x].size(); i++) {
int to = v[x][i];
if (to == fa) continue;
for (int j = tc; j >= 0; j--) {
LL A = F[j] + f[to];
LL B = j >= c[to]?F[j-c[to]]+g[to]:INF;
F[j] = min(A,B);
}
}
LL Min = INF;
for (LL i = 0; i <= tc; i++)
Min = min(Min,F[i] - i);
g[x] = 1LL*d[x] + Min;
Min = INF;
for (LL i = 0; i <= tc; i++)
if (i + c[fa] <= d[x])
Min = min(Min,F[i] - i);
else break;
f[x] = 1LL*(d[x] - c[fa]) + Min;
}
else {
LL t1,t2;
t1 = t2 = 0;
for (int i = 0; i < v[x].size(); i++) {
int to = v[x][i];
if (to == fa) continue;
if (!c[to]) t1 += f[to];
else {
if (g[to] == f[to]) t1 += g[to],t2 += c[to];
else t1 += f[to];
}
}
g[x] = t1 + max(1LL*d[x] - t2,0LL);
f[x] = t1 + max(1LL*d[x] - t2 - 1LL*c[fa],0LL);
}
}
int getint()
{
char ch = getchar();
int ret = 0;
while (ch < '0' || '9' < ch) ch = getchar();
while ('0' <= ch && ch <= '9')
ret = ret*10 + ch - '0',ch = getchar();
return ret;
}
int main()
{
#ifdef DMC
freopen("DMC.txt","r",stdin);
#endif
n = getint();
for (int i = 1; i <= n; i++) d[i] = getint();
for (int i = 1; i <= n; i++) {
c[i] = getint();
if (c[i] > 1) flag = 1;
}
for (int i = 1; i < n; i++) {
int x = getint();
int y = getint();
v[x].push_back(y);
v[y].push_back(x);
}
int Root = n/2;
Dfs(Root,0);
cout << g[Root];
return 0;
}