[BZOJ4472]-[Jsoi2015]salesman-贪心

说在前面

这个水题写了我一个白天= =
就因为建边忘了建双向边,以为所有给的边都是father->son的
简直*了狗(狗:woc关我啥事)


题目

BZOJ4472传送门

题目大意

给你一棵含有n个节点的树,每个节点有权值(可正可负),并且每个节点有路过次数上限。仅在第一次路过某个节点后,获得该节点的权值。现问从1号节点出发,在树上随便跑(可以不进入某些节点)后能获得的最大收益,并输出方案是否唯一。
若唯一,输出solution is unique
else,输出solution is not unique
方案唯一是指:至少有两个方案可以达到该权值,并且此两方案路过的节点不至少有一个点不同。

输入

第一行一个整数:n,代表总节点数。
第二行n-1个整数,代表第二到第n个点的权值
第三行n-1个整数,代表第二到第n各节点最大可路过次数
接下来n-1行,每行两个整数,代表此两节点之间有一条边。
9
-3 -4 2 4 -2 3 4 6
4 4 2 2 2 2 2 2
1 2
1 3
1 4
2 5
2 6
3 7
4 8
4 9
数据保证每个节点路过次数不小于二,并且从1可以到达任意一点,即整个图是联通的,不是森林。1号节点进入次数不限,权值为0.

输出

9
solution is unique


解法

一开始看到这个题可能会想到树形背包
然而这道题因为不确定选点的个数,因此背包的容积是不确定的,因此不能用背包做。
看到这里,请读者仔细思考一下该题正解:
·
·
·
·
·
·
·
·
·
·
·
如果还没想出来–>请回头看一下本文标题
.
.
.
.
.
.
.
.
.
.
.
可以发现,节点的权值有正有负,由于我们路过点的次数有限,那么我们肯定是尽量的把正权值的点选完

于是思路出来了:
递归处理每个子节点,然后把已经处理好的子节点的最优解从大到小排序,如果正权值已经选完,或者选的节点到达上限,就退出循环。

那么如何确定解是不是唯一呢?
在把子树的最大收益计算出来之后,是排了一遍 续....续1s!序的。如果当前选择的最后一个节点,和第一个me没选的节点收益一样,那么方案不唯一。如果me选中的节点中有收益为0的,那么方案也不唯一。


下面是自带大常数的代码:

其中,pro[i]代表的是点i的初始利润
dp数组为处理之后,路过改点获得的最大收益
isnotuni==is not unique
tim[i]表示i点最大可经过次数
其余的应该都能懂 ~

/* *************************************************************
    Problem: 4472
    User: Izumihanako
    Language: C++
    Result: Accepted
    Time:336 ms
    Memory:4340 kb
*************************************************************** */

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
int n,pro[100001],tim[100001],head[100001],tw=0;
int dp[100001],isnotuni[100001];
struct node{
    int pre,to;
}w[200001];

int comp(const pair<int,int > &a,const pair<int,int > &b){
    if(a.first<b.first)  return 0;
    return 1;
}

void In(int t1,int t2){
    w[++tw].pre=head[t1];
    w[tw].to=t2;
    head[t1]=tw;
}

void work(int u,int f){
    vector< pair<int,int> > Ve;
    Ve.clear();
    for(int i=head[u];i;i=w[i].pre){
        int v=w[i].to;
        if(v==f)    continue;
        work(v,u);
        Ve.push_back(make_pair<int,int>(dp[v],v));
    }
    sort( Ve.begin(), Ve.end(), comp);
    int i=0,R=Ve.size()-1;
    for(;i<=R&&i+2<=tim[u]&&Ve[i].first>=0;i++){
        isnotuni[u]|=isnotuni[Ve[i].second];
        dp[u]+=Ve[i].first;
    }
    if(i>0&&i!=Ve.size())
        isnotuni[u]|=(Ve[i-1].first==Ve[i].first ? 1 : 0);
    if(i>0&&Ve[i-1].first==0)
        isnotuni[u]=1;
    dp[u]+=pro[u];
}

inline int read()
{
    int data=0,w=1; char ch=0;
    while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0' && ch<='9') data=data*10+ch-'0',ch=getchar();
    return data*w;
}

int main(){
    int t1,t2;
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
        pro[i]=read();
    for(int i=2;i<=n;i++)
        tim[i]=read();
    tim[1]=0x3f3f3f3f;
    for(int i=2;i<=n;i++){
        t1=read();t2=read();
        In(t1,t2);
        In(t2,t1);
    }
    work(1,0);
    printf("%d\n",dp[1]);
    if(isnotuni[1]==1)
        printf("solution is not unique");
    else
        printf("solution is unique");
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值