Luogu P1262 【间谍网络】

  • P1262 【间谍网络】

Pre芝士\(of\) \(Tarjan\),下方也有引用的:\(\text{My Blog}\)


  • Prelude

题目给了你一个有向图,一个有环,而且只有部分点有点权,部分点没有的一个有向图。你现在使用点\(N\)点权值大小的代价可以进行:将这个点以及这个点可以到达的所有点放入点集\(\mathbf E\)中的操作,保证在点集\(\mathbf E\)中的所有点能到达的点也都在\(\mathbf E\)

如果

\[A \rightarrow B \]

\[A \in \mathbf E \]

则保证

\[B \in \mathbf E \]

那么问将所有点放入点集\(\mathbf E\)的最小代价是多少。如果不能输出\(\text{NO}\)以及无法放入的点中最小的那个,可以则输出\(\text{YES}\) & 最小代价。

我们先对题目细节进行分析:

首先是有向图

如果\(A\)可以购买,\(A\)可以到\(B\),那么花费\(Value_A\)可以使得\(\left\{A,B\right\} \subseteq \mathbf E\),可是不保证买下\(B\)就有\(A\)或者说B可能根本无法单独获取。

那么很显然我们不能点对点分析了,这样不能保证获得最小代价也不能保证复杂度优秀。

既然不能点对点分析,我们就考虑对于这题图的特性。

首先我会想到跑类似点权最短路的东西,但是似乎无法实现,因为如果存在不可到达的点,并不能很好判断,而且在跑最短路时我们如果将没有点权的点贪心为0,那么无法继续程序。稍微改下思路,这样反而变成一个裸的DFS,复杂度与实现难度变得奇奇怪怪

不过既然想到所谓点权最短路,那么我们就可以考虑到,对于点权最短路,我们必须考虑是否\(\text{DAG}\),不是\(\text{DAG}\)就不能完美实现记忆化搜索等点权计算的操作。毕竟点不是有向边,对于点来说很可能会陷入死循环。


  • Solution

结合以上信息,我们突然想到一个模板涵盖以上特点:P3387 【模板】缩点

还没学\(Tarjan\)的同学,欢迎来踩\(\text{My Blog}\)

如果我们对这题进行缩点,大部分问题就迎刃而解。

  • 对于每个缩点前的强连通分量,我们保证内部点互相通达,这样买了其中一个点,根据前面题意,保证所有强联通分量中的点都在\(\mathbf E\)中。

  • 其次,\(Tarjan\)算法可以很好解决输出\(\text{NO}\)的情况。缩点后的图不再有强连通分量,成为一个\(\text{DAG}\),那么怎样都的无法获得点就显而易见,对于这一题,记住并不是那些强连通分量内没有能购买的点就不能获得,如果缩点后的\(N_1 \rightarrow N_2\),即使\(N_2\)中没有点能购买,那么\(N_2\)还是可以通过“购买\(N_1\)中最便宜的那个点”这个操作来获取。简而言之,缩点后我们还是要走一遍该\(\text{DAG}\)来寻找点与点之间的相通性获得最优值。这个情况下最简单的做法自然是DFS,变成DAG后DFS的可行性变得更高。

对于这个题,缩点后的处理才是更为精髓的部分。


  • Code

我写这篇题解其实是因为对于我自己的做法,我很吃惊居然AC了,先缩点,缩点时记得记录这个强连通分量内最便宜的那个,如果没有能买的也要记录一下“-1”为不能买的。

我在缩完点后,干脆对每一个点做DFS,能走的点尽量走,DFS时顺便记忆化该点是否走过,如果走过则不再DFS。就直接从第一个点开始枚举,判断2点:1是是否有能购买的,没有你DFS也没用,有的话还要看之前的点是否涵盖他了。

这个很离谱对吧,真的很离谱,因为他是错的,他只有\(92pts\)(就这还92啊)

对于这种情况,很明显只需要买2就只需\(Val=4\)一石二鸟,但我for循环从1开始DFS我就会先买1在买2导致\(Val=7\)

然后我想了想。。。想了想。我想到可以换每个点都做一次起点,但那样貌似T了。

然后我常识性地把他倒着循环了一遍然后再做,取最小的那个。

就A了。所以就惊了并且摸了(指不再深究)。

\(\text{NO}\)的情况详见代码。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<stack>
#define N 300005
using namespace std;
int n,p,r;
int val[N],dfn[N],low[N],tot,cnt,sec[N],secs,size[N],head[N],rtsec[N],head1[N],x[N],y[N];
int ans;
stack <int> s;
bool in[N],oksec[N];
struct Rey
{
	int nxt,to;
}e[1000005],e1[1000005]; 
void add(int u,int v)
{
	e[++cnt].nxt=head[u];
	e[cnt].to=v;
	head[u]=cnt;
}
void add1(int u,int v)
{
	e1[++cnt].nxt=head1[u];
	e1[cnt].to=v;
	head1[u]=cnt;
}
void Tarjan(int x)
{
	dfn[x]=low[x]=++tot;
	in[x]=1;s.push(x);
	for(int i=head[x];i;i=e[i].nxt)
	{
		int go=e[i].to;
		if(!dfn[go])
		{
			Tarjan(go);
			low[x]=min(low[x],low[go]);
		}
		else if(in[go])low[x]=min(low[x],dfn[go]);
	}
	if(dfn[x]==low[x])
	{
		int tp;
		secs++;
		rtsec[secs]=1<<30;
		do
		{
			tp=s.top();
			s.pop();
			sec[tp]=secs;
			size[secs]++;
			in[tp]=0;
			if(val[tp]!=-1) 
			rtsec[secs]=min(rtsec[secs],val[tp]);
		}while(x!=tp);
	}
}//Tarjan缩点模板,详见另一篇学习笔记。
void DFS(int u)
{
	if(oksec[u]==1)return;
	oksec[u]=1;
	for(int j=head1[u];j;j=e1[j].nxt)
	{
		int go=e1[j].to;
		if(!oksec[go])
		DFS(go);
	}
}//DFS一直走并记录走过的。
int main()
{
	scanf("%d",&n);
	scanf("%d",&p);
	memset(val,-1,sizeof(val));
	memset(rtsec,-1,sizeof(val));
	for(int i=1;i<=p;i++)
	{
		int x;
		scanf("%d",&x);
		scanf("%d",&val[x]);
	}
	scanf("%d",&r); 
	for(int i=1;i<=r;i++)
	{
		scanf("%d %d",&x[i],&y[i]);
		add(x[i],y[i]); 
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])Tarjan(i);
	}
	cnt=0;
	for(int i=1;i<=r;i++)
	{
		if(sec[x[i]]!=sec[y[i]])
		{
			add1(sec[x[i]],sec[y[i]]); 
		}
        //缩点完也要重新建图,因为缩点后虽然没有环或者强联通分量,
        //但是存在买整个强连通分量同时获得另一个的情况。
	}
	for(int i=1;i<=secs;i++)
	{
		if((rtsec[i]!=-1&&rtsec[i]!=1<<30)&&oksec[i]==0){DFS(i);ans+=rtsec[i];}//DFS
	}
	bool ok=1;
	int outw=n+1;
	for(int i=1;i<=secs;i++)
	{
		if(!oksec[i])
		{
			if(ok==1)
			{puts("NO");ok=0;}//仍然没走的就一定是NO
			for(int j=1;j<=n;j++)
			{
				if(sec[j]==i&&val[j]==-1)
				{
					outw=min(outw,j);//找最小的
				}
			}
		} 
	}
	if(ok==0)
	{
		printf("%d\n",outw);
		return 0;
	}
	int tmp=ans;
	ans=0;
	memset(oksec,0,sizeof(oksec));
	for(int i=secs;i>=1;i--)
	{
		if((rtsec[i]!=-1&&rtsec[i]!=1<<30)&&oksec[i]==0)
        {DFS(i);ans+=rtsec[i];}
	}//倒着来一遍
	ans=min(ans,tmp);
	printf("YES\n%d\n",ans);
	return 0;
}

可能是数据比较水,如果有大佬可以Hack掉我会很感激并优化题解和代码。

posted @ 2020-11-21 09:37  Reywmp  阅读(95)  评论(0编辑  收藏  举报