bzoj 1179 抢掠计划atm (缩点+有向无环图DP)

3 篇文章 0 订阅

https://www.lydsy.com/JudgeOnline/problem.php?id=1179
题意:给定一张有向图,点有点权。给定起点(仅一个) s s 与终点集合(多个)T,问开始于起点、结束于终点集合中的一个点的路径中点权和的最大值。
题解:这里,第一步当然是tarjan缩点。
由于是开始于特定点,dp有些麻烦,建议采用spfa. 但是DP也能做,而且bfs和dfs都能做。只不过,有一些需要注意的地方,很容易写错。
bfs状态转移方程很简单: dp[i] d p [ i ] 表示从s出发到i这个点路径点权和的最大值, dp[i]=maxjind[i]dp[j]+a[i] d p [ i ] = m a x j ∈ i n d [ i ] d p [ j ] + a [ i ] , a[i]为点权。对于终点集合,我们可以建一个超级终点,每一个终点向超级终点连边,当然也可以朴素地枚举每个终点。但是,有一个细节问题坑了我很久:如果是这样写的:

Tarjan原图中的每一个点;
建新图;
bfs(); (队列中初始只有一个元素s)
ans = dp[超级终点];

则必然会WA. 后来我又对着标程查了好久,问了几位大佬,最后实在没办法对拍了一下,发现生成了这样一组数据成功卡掉自己,经简化如下——

3 2
1 2
3 2
5
1
3
1 1
2
Read 0,Expected 6.

正确的写法应该是:

Tarjan(s); //从s能到达的点
建新图;
bfs(); (队列中初始只有一个元素s)
ans = dp[超级终点];

只有第一行tarjan不一样。为什么呢?
原因是:对于一个点i, 刚才我们讨论的数食物链、字母最多出现次数、最长路径,都是要求 i i 的所有入边的起点都被更新后才可转移i(将其加入队列),bfs的时候从所有入度为0的点开始。但是,现在情况变了,如果我们只从s开始bfs,有一些s走不到的点将永远不会被更新,这样这个点所连向的边也不会被更新,就相当于这个点作废了。但是实际上这个点并未作废。如图所示,如果1号点为起点,2号点为终点,那3号点显然不会被访问到,但是由于2号点有1->2, 3->2两条入边,因此3号点不更新,2号点永远也不会被更新,答案一直是0.但是实际上,2还应该被1更新,因为起点1一定可以通过1->2进入2号点。
实际上,我们确保这种入边的起点先统计的顺序,原因是防止一个点有许多条转移的途径,但是只枚举了其中的一部分,最优解藏在另一部分中的这种情况。但是在这里,所谓“另一部分”是s点永远不可能达到的,因此答案为0,不可能成为最优解,反而还会拖累2号点的更新。
WA情况
AC代码:

#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 500000;
struct Edge
{
    int v,nxt; bool us;
} e[N+2],e0[N+2];
int fe[N+2],fe0[N+2];
int a[N+2],a0[N+2];
bool f[N+2];
int dfn[N+2],low[N+2];
int sta[N+2];
bool ins[N+2];
int clr[N+2];
int dp[N+2];
int que[N+2];
int ind[N+2];
bool f0[N+2];
int n,m,m0,s,p,tp,cnt,num;

void addedge(int u,int v)
{
    m++; e[m].v = v;
    e[m].nxt = fe[u]; fe[u] = m;
}

void addedge0(int u,int v)
{
    m0++; e0[m0].v = v; ind[v]++;
    e0[m0].nxt = fe0[u]; fe0[u] = m0;
}

void Tarjan(int u)
{
    cnt++; dfn[u] = low[u] = cnt; ins[u] = true;
    tp++; sta[tp] = u;
    for(int i=fe[u]; i; i=e[i].nxt)
    {
        if(dfn[e[i].v]==0) {Tarjan(e[i].v); low[u] = min(low[u],low[e[i].v]);}
        else if(ins[e[i].v]==true) {low[u] = min(low[u],dfn[e[i].v]);}
    }
    if(low[u]==dfn[u])
    {
        num++; clr[u] = num; a0[num] = a[u]; f0[num] = f[u];
        while(sta[tp]!=u)
        {
            ins[sta[tp]] = false;
            clr[sta[tp]] = num;
            a0[num] += a[sta[tp]];
            f0[num] = f[sta[tp]]||f0[num];
            tp--;
        }
        ins[u] = false; tp--;
    }
}

void bfs()
{
    int head = 1,tail = 1; que[tail] = clr[s]; dp[clr[s]] = a0[clr[s]];
    while(head<=tail)
    {
        int cur = que[head]; head++;
        for(int i=fe0[cur]; i; i=e0[i].nxt)
        {
            if(e0[i].us==false)
            {
                ind[e0[i].v]--; e0[i].us = true; dp[e0[i].v] = max(dp[e0[i].v],dp[cur]+a0[e0[i].v]);
                if(ind[e0[i].v]==0)
                {
                    tail++; que[tail] = e0[i].v;
                }
            }
        }
    }
}

int main()
{
    int mm; m = 0; scanf("%d%d",&n,&mm);
    for(int i=1; i<=mm; i++)
    {
        int x,y; scanf("%d%d",&x,&y); addedge(x,y);
    }
    for(int i=1; i<=n; i++) scanf("%d",&a[i]);
    scanf("%d%d",&s,&p);
    for(int i=1; i<=p; i++) {int x; scanf("%d",&x); f[x] = true;}
    cnt = 0; Tarjan(s);
    //for(int i=1; i<=n; i++) if(!dfn[i]) Tarjan(i);
    num++;
    for(int i=1; i<=n; i++)
    {
        for(int j=fe[i]; j; j=e[j].nxt)
        {
            if(clr[i]!=0 && clr[e[j].v]!=0 && clr[i]!=clr[e[j].v])
            {
                addedge0(clr[i],clr[e[j].v]);
            }
        }
    }
    for(int i=1; i<=num; i++) {if(f0[i]==true) addedge0(i,num);}
    bfs();
    printf("%d\n",dp[num]);
    return 0;
}

dfs当然也能做,此处不再赘述。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: bzoj作为一个计算机竞赛的在线评测系统,不仅可以提供大量的题目供程序员练习和学习,还可以帮助程序员提升算法和编程能力。为了更好地利用bzoj进行题目的学习和刷题,制定一个bzoj做题计划是非常有必要的。 首先,我们需要合理安排时间,每天留出一定的时间来做bzoj的题目。可以根据自己的时间安排,每天挑选适量的题目进行解答。可以先从难度较低的题目开始,逐渐提高难度,这样既能巩固基础知识,又能挑战自己的思维能力。 其次,要有一个计划和目标。可以规划一个每周或每月的题目数量目标,以及每个阶段要学习和掌握的算法知识点。可以根据bzoj的题目分类,如动态规划、图论、贪心算法等,结合自己的实际情况,有针对性地选择题目进行学习。 此外,要充分利用bzoj提供的资源。bzoj网站上有很多高质量的题解和优秀的解题代码,可以参考和学习。还有相关的讨论区,可以与其他程序员交流和讨论,共同进步。 最后,要坚持并保持思考。做题不是单纯为了刷数量,更重要的是学会思考和总结。遇到难题时,要有耐心,多思考,多尝试不同的解法。即使不能一次性解出来,也要学会思考和分析解题过程,以及可能出现的错误和优化。 总之,bzoj做题计划的关键在于合理安排时间、制定目标、利用资源、坚持思考。通过有计划的刷题,可以提高算法和编程能力,并培养解决问题的思维习惯,在计算机竞赛中取得更好的成绩。 ### 回答2: bzoj做题计划是指在bzoj这个在线测评系统上制定一套学习和刷题的计划,并且将计划记录在excel表格中。该计划主要包括以下几个方面的内容。 首先是学习目标的设定。通过分析自己的水平和知识缺口,可以设定一个合理的目标,比如每天解决一定数量的题目或者提高特定的算法掌握程度。 其次是题目选择的策略。在excel表格中可以记录下自己选择的题目编号、题目类型和难度等信息。可以根据题目的类型和难度来安排每天的刷题计划,确保自己可以逐步提高技巧和解题能力。 然后是学习进度的记录和管理。将每天的完成情况记录在excel表格中,可以清晰地看到自己的学习进度和任务完成情况。可以使用图表等功能来对学习进度进行可视化展示,更好地管理自己的学习计划。 同时,可以在excel表格的备注栏中记录下每道题目的解题思路、关键点和需要复习的知识点等信息。这样可以方便自己回顾和总结,巩固所学的知识。 最后,可以将excel表格与其他相关资料进行整合,比如算法教材、题目解析和学习笔记等。这样可以形成一个完整的学习档案,方便自己进行系统的学习和复习。 总之,bzoj做题计划excel的制定和记录可以帮助我们更加有条理和高效地进行学习和刷题。通过合理安排学习目标和题目选择策略,记录学习进度和思路,并整合其他学习资料,我们可以提高自己的解题能力,并在bzoj上取得更好的成绩。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值