salesman,动态规划带一点点贪心。

题目直接链接

分析一下:

  这题题意还是比较明白的(少见的一道中文题),他的意思就是:有这么一个无向图:保证联通且点与点直接有唯一的简单路径(说白了就是棵树,根节点是1),每个节点有一个权值(有正有负)和最多经过的次数(>=2),求从根到根的走法中能拿到的最大权值(每个权值只能拿一次,根没有权值,且不限次数)。

  题意还是这么长。。。不过其实每一句话都是比较通俗的,大家应该都能理解题意。

  既然是一棵树,那就先想一想有关树的东西(不过思维不要僵化,也不一定就用有关树的知识),显然最小生成树,倍增lca啥的没啥用,而且求最优很容易想到dp,但是到底行不行呢,还是要试试嘛。

  我们想一想,这个次数可以限制什么呢?首先你要想到这里回去,就要使用一次停留的次数,如果你还要再去一个儿子,那么你还要多停留一次,同理,去两个就要多停留两次。注意,是多停留,也就是说原先的一次该停还是还是要停的,于是,我们就可以从儿子里面选择次数-1个最优的加进去,可是儿子最优的要哪里来呢,提前处理出来,诶,有感觉了Dfs加Dp,也就是树形Dp(其实这里并不是很纯正的Dp,因为可能会t掉,所以稍有不同)。

  基本的思路有了,那我们来想一下怎么转移状态,当然转移前要先定义好,这个的定义应该很简单:Dp[i]表示加入i之后可以加的最大权值。那Dp[i]=max(0,前次数-1大的儿子的最优之和(有一点点贪心的感觉)+i的权值),貌似还挺好写的,但是怎么找到前次数-1大的儿子的最优呢?这个就是代码能力的问题了。。。这里处理方法很多:可以维护一个堆,然后一个一个push进去(因为要边进行dfs边push,所以加一个参数,重定义一下<就好了),也可以Dfs和处理分开,先dfs,然后再处理,这样就不会出现覆盖前面数据的问题了,还有就是,可以我开一个n的数组,然后边dfs边分配内存,也可以,当然,给每一个节点开一个数组就不太好了,你也开不下。

  好了??其实才一半,还要输出有没有多解呢,先看一个错误代码(这里的数据真的有一点水)

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=100000+10;
int val[maxn];
int cs[maxn];
int Dp[maxn];
int jl[maxn];
bool Dj[maxn];
struct E{
    int to;
    int next;
    int tree;
    E(){
        to=next=0;
        tree=1;
    }
}ed[maxn*2];
int head[maxn];
int tot;
int cfr;
void J(int a,int b){
    tot++;
    ed[tot].to=b;
    ed[tot].next=head[a];
    head[a]=tot;
}
bool Cm(int a,int b){
    return Dp[a]<Dp[b];
}
void Dfs(int a){
    int js=cfr;
    int fr=cfr;
    for(int i=head[a];i;i=ed[i].next)
        if(ed[i].tree){
            js++;
            jl[js]=ed[i].to;
        }
    cfr=js;
    for(int i=head[a];i;i=ed[i].next)
        if(ed[i].tree){
            ed[i%2?(i+1):(i-1)].tree=0;
            Dfs(ed[i].to);
        }
    if(js==fr){
        Dp[a]=max(0,val[a]);
        if(val[a]==0)
            Dj[a]=1;
        return;
    }
    sort(jl+1+fr,jl+js+1,Cm);
    int ans=0;
    bool dj=0;
    int E=fr;
    for(int i=js;i>fr&&js-i+1<=cs[a]-1;i--){
        ans+=Dp[jl[i]];
        dj|=Dj[jl[i]];
        E=i;
    }
    if(ans+val[a]<0)
        return;
    if(!(ans+val[a])){
        Dj[a]=1;
        return;
    }
    else{
        Dp[a]=val[a]+ans;
        Dj[a]=dj;
    }
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
        scanf("%d",&val[i]);
    for(int i=2;i<=n;i++)
        scanf("%d",&cs[i]);
    cs[1]=maxn;
    int js1,js2;
    for(int i=1;i<=n-1;i++){
        scanf("%d%d",&js1,&js2);
        J(js1,js2);
        J(js2,js1);
    }
    Dfs(1);
    printf("%d\n",Dp[1]);
    if(Dj[1])
        printf("solution is not unique");
    else
        printf("solution is unique");
    return 0;
}

错的ac代码

这个竟然过了。。。大家可以看看有什么问题,我也不再加注释了

能卡掉它的数据:

5
2 2 2 2
2 2 2 2
1 2
1 3
2 4
2 5

正确:

6

solution is not unique

它的输出:

6

solution is unique

大家可不要学。。。

好了,那我们到底怎么判断有无多解呢?

选择的儿子有多解,它有多解。不选的儿子与选的相同,有多解。选出0来,有多解。

所以代码是?:

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=100000+10;
int val[maxn];
int cs[maxn];
int Dp[maxn];
int jl[maxn];
bool Dj[maxn];
struct E{
    int to;
    int next;
    int tree;
    E(){
        to=next=0;
        tree=1;
    }
}ed[maxn*2];
int head[maxn];
int tot;
int cfr;
void J(int a,int b){
    tot++;
    ed[tot].to=b;
    ed[tot].next=head[a];
    head[a]=tot;
}
bool Cm(int a,int b){
    return Dp[a]<Dp[b];
}
void Dfs(int a){
    int js=cfr;
    int fr=cfr;
    for(int i=head[a];i;i=ed[i].next)
        if(ed[i].tree){
            js++;
            jl[js]=ed[i].to;
        }
    cfr=js;
    for(int i=head[a];i;i=ed[i].next)
        if(ed[i].tree){
            ed[i%2?(i+1):(i-1)].tree=0;
            Dfs(ed[i].to);
        }
    if(js==fr){
        Dp[a]=max(0,val[a]);
        if(val[a]==0)
            Dj[a]=1;
        return;
    }
    sort(jl+1+fr,jl+js+1,Cm);
    int ans=0;
    bool dj=0;
    int E=fr;
    for(int i=js;i>fr&&js-i+1<=cs[a]-1;i--){
        ans+=Dp[jl[i]];
        dj|=Dj[jl[i]];
        E=i;
    }
    if(ans+val[a]<0)
        return;
    if(!(ans+val[a])){
        Dj[a]=1;
        return;
    }
    else{
        Dp[a]=val[a]+ans;
        Dj[a]=dj;
        if(E>fr+1&&Dp[jl[E]]==Dp[jl[E-1]]&&(Dp[jl[E]]||Dj[jl[E]]))
            Dj[a]=1;
        if(!Dp[jl[E]])
            for(int i=E-1;i>fr;i--)
                if(!Dp[jl[i]]&&Dj[jl[i]])
                    Dj[a]=1;
    }
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
        scanf("%d",&val[i]);
    for(int i=2;i<=n;i++)
        scanf("%d",&cs[i]);
    cs[1]=maxn;
    int js1,js2;
    for(int i=1;i<=n-1;i++){
        scanf("%d%d",&js1,&js2);
        J(js1,js2);
        J(js2,js1);
    }
    Dfs(1);
    printf("%d\n",Dp[1]);
    if(Dj[1])
        printf("solution is not unique");
    else
        printf("solution is unique");
    return 0;
}

其实还是有问题,不过那个都过了,这个按理说也能过。。。

问题在哪里,大家仔细研究一下,这个问题有点难发现。。。

好的,最后是最后的代码(终于有注释了):

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=100000+10;
int val[maxn];
int cs[maxn];
int Dp[maxn];
int jl[maxn];//排序用
bool Dj[maxn];
struct E{
    int to;
    int next;
    int tree;
    E(){
        to=next=0;
        tree=1;
    }
}ed[maxn*2];
int head[maxn];
int tot;
int cfr;
void J(int a,int b){
    tot++;
    ed[tot].to=b;
    ed[tot].next=head[a];
    head[a]=tot;
}
bool Cm(int a,int b){
    return Dp[a]<Dp[b];
}
void Dfs(int a){
    int js=cfr;
    int fr=cfr;
    for(int i=head[a];i;i=ed[i].next)
        if(ed[i].tree){
            js++;
            jl[js]=ed[i].to;
        }
    cfr=js;//先提前“申请空间”。
    for(int i=head[a];i;i=ed[i].next)
        if(ed[i].tree){
            ed[i%2?(i+1):(i-1)].tree=0;
            Dfs(ed[i].to);
        }
    if(js==fr){//叶子节点,特判掉
        Dp[a]=max(0,val[a]);
        if(val[a]==0)
            Dj[a]=1;
        return;
    }
    sort(jl+1+fr,jl+js+1,Cm);
    int ans=0;
    bool dj=0;
    int E=fr;
    for(int i=js;i>fr&&js-i+1<=cs[a]-1;i--){
        ans+=Dp[jl[i]];
        dj|=Dj[jl[i]];//记录有无多解
        E=i;
    }
    if(ans+val[a]<0)//0都不到,直接不走它,也没多解
        return;
    if(!(ans+val[a])){//是0,有多解(走与不走)
        Dj[a]=1;
        return;
    }
    else{//大于0
        Dp[a]=val[a]+ans;
        Dj[a]=dj;
        if(E>fr+1&&Dp[jl[E]]==Dp[jl[E-1]]&&(Dp[jl[E]]||Dj[jl[E]]))//如果儿子有相同的(注意,相同的0还要特殊处理)
            Dj[a]=1;
        if(!Dp[jl[E]])//是0
            for(int i=E-1;i>fr;i--)//找一遍看看有没有多解的0
                if(!Dp[jl[i]]&&Dj[jl[i]])
                    Dj[a]=1;
    }
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
        scanf("%d",&val[i]);
    for(int i=2;i<=n;i++)
        scanf("%d",&cs[i]);
    cs[1]=maxn;
    int js1,js2;
    for(int i=1;i<=n-1;i++){
        scanf("%d%d",&js1,&js2);
        J(js1,js2);
        J(js2,js1);
    }
    Dfs(1);
    printf("%d\n",Dp[1]);
    if(Dj[1])
        printf("solution is not unique");
    else
        printf("solution is unique");
    return 0;
}

原文地址:https://www.cnblogs.com/wish-all-ac/p/12632895.html

时间: 2024-08-30 16:31:16

salesman,动态规划带一点点贪心。的相关文章

【ACM】动态规划?剪枝?贪心?jobdu 1082

题目描述: 使用代理服务器能够在一定程度上隐藏客户端信息,从而保护用户在互联网上的隐私.我们知道n个代理服务器的IP地址,现在要用它们去访问m个服务器.这 m 个服务器的 IP 地址和访问顺序也已经给出.系统在同一时刻只能使用一个代理服务器,并要求不能用代理服务器去访问和它 IP地址相同的服务器(不然客户端信息很有可能就会被泄露).在这样的条件下,找到一种使用代理服务器的方案,使得代理服务器切换的次数尽可能得少. 输入: 每个测试数据包括 n + m + 2 行.    第 1 行只包含一个整数

动态规划-带权区间问题

一.动态规划算法的定义: 为了着手开发一个动态规划算法,我们需要一组从初始问题导出的满足某些基本性质的子问题. 只存在多项式个子问题 可以容易的从子问题的解计算出初始问题的解 在子问题中,从"最小"到"最大"存在一种自然的顺序,与一个容易计算的递推公式相联系.这个递推公式允许我们从某些更小的子问题的解来确定一个子问题的解. 二.带权区间调度问题: 我们有N个需求,标记为1,2,3,......,N,每个需求指定一个开始时间si,结束时间fi ,每个需求i有一个权值v

[CTSC2010]星际旅行(带反悔的贪心)

题目 有一棵树,限制从每个点出发的次数最多为\(c_i\),对于每个点i,求1到i的路径最多经过多少次边\(,(n\leq 40000)\),保证每个点的\(c\)大于其入度 解法1 直接莽?拆点,中间连容量为\(c_i\)费用为0的边,点与点之间连容量为inf费用为1的边,从1到每个点i跑一次最大费用最大流即可 显然过不了本题数据 解法2 考虑树形DP解决这个网络流问题 递推解决问题?本题难点在于:确定递推顺序然后确定一个点的答案 由于每个点的\(c\)都大于其入度,那么从1开始一定可以将所有

贪心算法和动态规划算法

动态规划和贪心算法都是一种递推算法 即均由局部最优解来推导全局最优解 (不从整体最优解出发来考虑,总是做出在当前看来最好的选择.) 不同点: 贪心算法 与动态规划的区别:贪心算法中,作出的每步贪心决策都无法改变,由上一步的最优解推导下一步的最优解,所以上一部之前的最优解则不作保留. 能使用贪心法求解的条件:是否能找出一个贪心标准.我们看一个找币的例子,如果一个货币系统有三种币值,面值分别为一角.五分和一分,求最小找币数时,可以用贪心法求解:如果将这三种币值改为一角一分.五分和一分,就不能使用贪心

活动选择问题(贪心算法vs动态规划)

活动选择问题贪心算法vs动态规划 基础知识 1-1动态规划 1-2贪心算法 1-3贪心算法vs动态规划 活动选择问题描述 活动选择问题最优子结构 活动选择问题算法设计 4-1贪心算法之选择最早结束活动 4-1-1递归贪心算法 4-1-2迭代的方式进行 4-2贪心算法之选择最短时长活动 4-3动态规划方法实现 4-3-1自上而下的实现 4-3-2自下而上的实现 结论 活动选择问题(贪心算法vs动态规划) 1.基础知识 在讲解活动选择问题之前,我们首先来介绍一动态规划和贪心算法的基础知识 1-1.动

算法导论——lec 13 贪心算法与图上算法

之前我们介绍了用动态规划的方法来解决一些最优化的问题.但对于有些最优化问题来说,用动态规划就是"高射炮打蚊子",采用一些更加简单有效的方法就可以解决.贪心算法就是其中之一.贪心算法是使所做的选择看起来是当前最佳的,期望通过所做的局部最优选择来产生一个全局最优解. 一. 活动选择问题 [问题]对几个互相竞争的活动进行调度:活动集合S = {a1, a2, ..., an},它们都要求以独占的方式使用某一公共资源(如教室),每个活动ai有一个开始时间si和结束时间fi ,且0 ≤ si &

贪心选择算法

p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ".PingFang SC"; color: #454545 } p.p2 { margin: 0.0px 0.0px 2.0px 0.0px; font: 14.0px ".PingFang SC"; color: #454545 } p.p3 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px "Helv

理解动态规划、分治法和贪心法

本文转自:http://www.cnblogs.com/airwindow/p/4067902.html http://hi.baidu.com/35661327/blog/item/d5463e17f1e8d011972b439c.html 动态规划.分治法和贪心法都是利用求解子问题,而后利用子问题求解更上层问题,最终获得全局解决方案的方法. 但是三者的应用场景和性质却存在着极大的不同: 1.分治法 很容易与动态规划问题混淆,但两者却有着本质上的差异. 分治法采用的是递归的思想来求解问题,两个

算法导论-动态规划-钢条切割

动态规划通常用于解决最优化问题,在这类问题中,通过做出一组选择来达到最优解.在做出每个选择的同时,通常会生成与原问题形式相同的子问题.当多于一个选择子集都生成相同的子问题时,动态规划技术通常就会很有效,其关键技术就是对每个这样的子问题都保存其解,当其重复出现时即可避免重复求解. 钢条切割问题 Serling公司购买长钢条,将其切割为短钢条出售.切割工序本身没有成本支出.公司管理层希望知道最佳的切割方案.假定我们知道Serling公司出售一段长为i英寸的钢条的价格为pi(i=1,2,…,单位为美元