HZOJ Atm || P3627 [APIO2009] 抢掠计划

发布时间 2023-08-21 07:43:18作者: minecraft114514

(HZOJ Atm) | | (P3627 [APIO2009] 抢掠计划)

  • 题目似乎不是非常正义?
  • \(N\) 个点, \(M\) 条有向边(为什么路还不能倒着走?), \(P\) 个酒吧。点权(也就是 \(ATM\) 取款机里的钱)为 \(a_i\) 。要求从起点 \(S\) 出发,到任意一个酒吧庆祝胜利(终点)。其中每条边可以跑任意次,但是 \(Atm\) 里不会再有钱了(因为已经空了)...(逃)
  • 很容易想到最短路,可以用 \(spfa\) 求最长路,以 \(S\) 为源点,任意一个酒吧为终点,求 \(dis\) 的最大值。
  • 确实可以用 \(spfa\) 求最短路,但是数据范围 \(N, M \le 5\times 10^5\)\(0 \le a_i \le 4000\)\(5 \times 10^5\) 个点直接打 \(spfa\) 怎么想也会超时的。
  • 因此我们需要用 \(tarjan\) 缩点,将可以联通的一些点看做一个点,这样就可以让点的数量变少,\(spfa\) 就能更快
  • 首先建原始图,不过需要用数组存下每条边的起点和终点 \((有用)\) ,之后正常跑一遍 \(tarjan\) 但是需要将同一个强连通块里的点缩成一个点,由于终点是 \(P\) 个酒吧的其中一个,因此也需要存下来酒吧在哪一个强连通块。由于点被缩成更大的点,因此这个点里 \(Atm\) 中的$也会更多,所以还需要将原点权加入到缩点的点权中。
  • 跑完 \(tarjan\) 后,还需要重新建图,可以初始化原结构体,也可以用新结构体。
  • 建图操作就是在两个强连通块中建边,之前将每条边的起始点存在了 \(x,y\) 数组中,如果起点和终点不在一个强连通块,就可以在这里建一条边,
for(i=1;i<=m;++i)
        if(bj[x[i]]!=bj[y[i]])
            add(bj[x[i]],bj[y[i]]);
  • 建完图就可以跑 \(spfa\) ,用缩完点后的新图跑 \(spfa\) 就不会超时了。
  • 最后枚举从起点到终点 (每个酒吧所在的强连通块) 所获得的钱数,取一个最大值即可。
#include<cstdio>
#include<cstring>
#include<stack>
#include<queue>
#define re register
using std::stack;
using std::queue;
inline int read()
{
    int x=0;bool f=1;
    char s=getchar();
    for(;s<'0'||s>'9';s=getchar()) if(s=='-') f=0;
    for(;'0'<=s&&s<='9';s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f?x:~x+1;
}
//相当于快读
void write(int x)
{
    if(x<0) x=~x+1,putchar('-');
    if(x>9)
    {
        long long q=0x1999999a;
        write(int((q*x)>>32));
    }
    putchar(x%10+48);
}
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
int dfn[500001],low[500001],cnt,vis[500001];
int bj[500001],tot;
int n,m;
int dis[500001],v[500001],V[500001];
int num,head[500001];
bool club[500001],win[500001];
int c[500001],start;
struct aa{int next,to;}e[1000001];
inline void add(int from,int to){e[++num]={head[from],to};head[from]=num;}
stack<int>s;
void tarjan(int x)
{
	dfn[x]=low[x]=++cnt;
	s.push(x);
	vis[x]=1;
	for(re int i=head[x];i;i=e[i].next)
	{
		re int y=e[i].to;
		if(!dfn[y])tarjan(y),low[x]=min(low[x],low[y]);
		else if(vis[y])low[x]=min(low[x],dfn[y]);
	}
	if(low[x]==dfn[x])
	{
		re int z;
		++tot;
		while(x!=z)
		{
			z=s.top();
			vis[z]=0;
			bj[z]=tot;
            if(club[z])win[tot]=1;
            V[tot]+=v[z];
			s.pop();
		}
	}
}
int flag[500001],pos;
int x[500001],y[500001];
void spfa(int x)
{
	re int i,k;
	queue<int> q;
    dis[x]=V[x];
	q.push(x);
	flag[x]=1;
	while(!q.empty())
	{
		k=q.front();
		for(i=head[k];i;i=e[i].next)
        {
            re int y=e[i].to;
            if(dis[y]<dis[k]+V[y])
            {
                dis[y]=dis[k]+V[y];
                if(!flag[y])
                {
                    q.push(y);
                    flag[y]=1;
                }
            }
        }
		flag[k]=0;
		q.pop();
	}
}
int main()
{
    re int i,j,k,l,ls;
    n=read(),m=read();
    for(i=1;i<=m;++i)x[i]=read(),y[i]=read(),add(x[i],y[i]);
	for(i=1;i<=n;++i)v[i]=read();
    start=read(),pos=read();
	for(i=1;i<=pos;++i)ls=read(),club[ls]=1;
	for(i=1;i<=n;++i)if(!dfn[i])tarjan(i);
    num=0;
    memset(e,0,sizeof(e));
    memset(head,0,sizeof(head));
    for(i=1;i<=m;++i)
        if(bj[x[i]]!=bj[y[i]])
            add(bj[x[i]],bj[y[i]]);
    spfa(bj[start]);
    re int ansss=0;
    for(i=1;i<=tot;++i)if(win[i])ansss=max(ansss,dis[i]);
    write(ansss);
    return 0;
}
  • 这道题就这样被水过去了