zoukankan      html  css  js  c++  java
  • 题解 P3627 【[APIO2009]抢掠计划】

    这道题前面的思路和其他题解一样,都是tarjan缩点。然后是要求金币的最大值,我用的是记忆化搜索,用jiyi[i]来表示从编号为i的联通块出发,最多可以抢到多少钱,搜索时如果遇到已经搜过的点(知道从那个点开始最多能抢多少钱)就直接return。因为记忆化搜索每个点只会遍历一次,所以记忆化搜索的复杂度应该是O(n)。

    另外我的码风有点奇怪,先解释一下:stk[tk]分别是栈和栈的指针(tarjan用),num表示联通块个数,tim时间戳,shuyu[i]表示点i属于哪个联通块,he[]表示某个联通块内总共有多少钱,vis[]表示一个点是否在栈中,jiuba[]表示某个点是否有酒吧,suojb[]表示缩点后的连通块中是否有酒吧。bian[],head[],ecnt都是缩点前图中的边,如果后面有后缀x,则表示缩点后新图中的边;

    #include<bits/stdc++.h>
    using namespace std;
    const int N=500005;
    int n,m,ecnt,s,p,tk,num,tim,ecntx,ans;
    int qian[N],head[N],dfn[N],low[N],stk[N],shuyu[N],he[N],headx[N],jiyi[N];
    bool vis[N],jiuba[N],suojb[N];
    struct aaa
    {
    	int to,nxt;
    }bian[N],bianx[N];
    inline int read()//标准快读
    {
        int x=0;bool f=0;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=1;c=getchar();}
        while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
        return f?-x:x;
    }
    inline void add(int a,int b)//链式前向星加边;
    {
    	bian[++ecnt].to=b;
    	bian[ecnt].nxt=head[a];
    	head[a]=ecnt;
    }
    inline void addx(int a,int b)//缩点后联通块与联通块加边;
    {
    	bianx[++ecntx].to=b;
    	bianx[ecntx].nxt=headx[a];
    	headx[a]=ecntx;
    }
    void tarjan(int u)//标准tarjan
    {
    	dfn[u]=low[u]=++tim;
    	stk[++tk]=u;vis[u]=1;
    	for(int i=head[u];i;i=bian[i].nxt)
    	{
    		int v=bian[i].to;
    		if(!dfn[v])
    			tarjan(v),low[u]=min(low[u],low[v]);
    		else if(vis[v])low[u]=min(low[u],low[v]);
    	}
    	if(dfn[u]==low[u])
    	{
    		num++;
    		while(stk[tk]!=u)
    		{
    			shuyu[stk[tk]]=num;//记录点属于的联通块;
    			vis[stk[tk]]=0;
    			stk[tk]=0;tk--;
    		}
    		shuyu[stk[tk]]=num;//此时栈顶还有一个点在此联通块中,再执行一次
    		vis[stk[tk]]=0;
    		stk[tk]=0;tk--;
    	}
    }
    void suo()//缩点;
    {
    	for(int i=1;i<=n;i++)
    	{
    		he[shuyu[i]]+=qian[i];//更新每个联通块内的总钱数
    		if(jiuba[i])suojb[shuyu[i]]=1;更新联通块内是否有酒吧
    		for(int j=head[i];j;j=bian[j].nxt)
    		{
    			int v=bian[j].to;
    			if(shuyu[i]!=shuyu[v])
    				addx(shuyu[i],shuyu[v]);//如果起点和终点不在同一个联通块,就在这两个联通块之间加边;
    		}
    	}
    }
    int dfs(int dian)//缩了点,新图为有向无环图,此时才能dfs
    {
    	if(jiyi[dian])return jiyi[dian];//如果这个联通块已经被搜索过,即我们知道从这个点开始的最大钱数,就直接返回;
    	if(headx[dian]==0&&suojb[dian]==1)//如果这个联通块出度为0并且此处有酒吧,就可以在此处停止,返回这个联通块的总钱数;
    	{
    		jiyi[dian]=he[dian];
    		return he[dian];
    	}
    	if(headx[dian]==0&&suojb[dian]==0)return 0;//如果这个联通块出度为0但是没有酒吧,就不能停止在此处,所以它对答案的贡献是0;
    	for(int i=headx[dian];i;i=bianx[i].nxt)
    	{
    		jiyi[dian]=max(jiyi[dian],dfs(bianx[i].to));//一个联通块的多个出度中取一个最大的;
    	}
    	if(jiyi[dian]==0&&suojb[dian]==0)return 0;
       	//注意:我们此时少考虑了一种情况,如果一条路径上一连串的点都没有酒吧,那么劫匪是不可以在这条路径上停下的,但如果没了这句话,程序还是会走这条路;加上这句话,如果后面的点没有酒吧 并且 此处也没有酒吧,那么它对答案的贡献是0,直接return 0;
    	jiyi[dian]+=he[dian];//把自己的点权加进记忆化数组中;
    	return jiyi[dian];//返回从这点开始的最大钱数;
    }
    int main()
    {
    	int a,b;
    	n=read();m=read();
    	for(int i=1;i<=m;i++)
    	{
    		a=read();b=read();
    		add(a,b);
                    //有个奇怪的错误,不能写add(read(),read());否则会出错,我也不知道为什么;
    	}
    	for(int i=1;i<=n;i++)
    		qian[i]=read();
    	s=read();p=read();
    	for(int i=1;i<=p;i++)
    		jiuba[read()]=1;
    	for(int i=1;i<=n;i++)
    		if(!dfn[i])
    			tarjan(i);
    	suo();
    	ans=dfs(shuyu[s]);
    	cout<<ans;
    	return 0;
    }
    

      

  • 相关阅读:
    优化MyBatis配置文件中的配置
    Java多线程---同步与锁
    Runtime.getRuntime().exec()
    java ---线程wait/notify/sleep/yield/join
    redis配置详情
    httpcline
    线程
    Bootstrap学习(一)
    springmvc注解配置
    salesforce上上传和导出.csv格式文件
  • 原文地址:https://www.cnblogs.com/oierjzy/p/11229466.html
Copyright © 2011-2022 走看看