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;
}
posted @ 2020-08-04 11:15  LawrenceD  阅读(109)  评论(0)    收藏  举报