JSOI2015 saleman
题目描述
找到一条从根出发回到根的路径,使得每个点经过的次数不超过它的限制,并且点权和最大。 多次经过算一次贡献。
解题思路
首先发现一个点最多能走的儿子数是\(t_i-1\)个,(\(t_i\)是每个点经过次数的限制)
这样有个比较好想的背包dp
然后,改成贪心就好了,看起来就没有后效性。
最后记有无多重方案,要如果出现0,肯定有,否则看剩下的数中有没有和最后一次选的数一样的,算贡献的时候继承一下儿子的就好。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e5 + 11;
int n, K;
int t[N];
LL w[N], f[N];
bool flag[N];
vector<int> G[N];
bool cmp(int x, int y){
if(f[x] != f[y])return f[x] > f[y];
else return flag[x] > flag[y];
}
void dfs(int u, int fa){
for(int v : G[u]){
if(v == fa)continue;
dfs(v, u);
}
static vector<int> son;
son.clear();
for(int v : G[u]){
if(v == fa)continue;
son.push_back(v);
}
sort(son.begin(), son.end(), cmp);
if(u != 1)t[u] = min(t[u], (int)G[u].size() - 1);
f[u] = w[u]; int lst = -1;
for(int i = 0;i < t[u]; i++){
int v = son[i];
if(f[v] > 0){
f[u] += f[v]; lst = f[v];
flag[u] |= flag[v];
}
else if(f[v] == 0)flag[u] |= 1;
}
for(int i = t[u];i < son.size(); i++){
int v = son[i];
if(lst == f[v])flag[u] |= 1;
}
//printf("u=%d f=%lld\n", u, f[u]);
}
int main(){
freopen("4472.in", "r", stdin);
freopen("4472.out", "w", stdout);
cin>>n;
int u, v;
for(int i = 2;i <= n; i++){
scanf("%lld", &w[i]);
}
for(int i = 2;i <= n; i++){
scanf("%d", &t[i]);
t[i]--;
}
for(int i = 1;i < n; i++){
scanf("%d%d", &u, &v);
G[u].push_back(v); G[v].push_back(u);
}
t[1] = G[1].size();
dfs(1, 1);
cout<<f[1]<<endl;
if(flag[1]){
puts("solution is not unique");
}
else puts("solution is unique");
return 0;
}