CodeVS&Luogu 间谍网络

移步到新Blog查看此文章的更新




这个题大概思路就是一发裸的Tarjan然后建一个缩点的图。

读入之后先来一发Tarjan(注意有可能有多个连通图),记录下每个点属于的强连通分量。

然后缩点,建一个DAG,这时根据DAG的结构,肯定存在若干入度为0的点

下面就是重点了,只需要收买(也就是计算这个强连通分量中Minimize的Money)所有入度为0的缩点,也叫根点(自造的词)就一定可以抓完 全图的 间谍。

假如收买了根点之后无法收买缩点u,那么缩点u一定不是根点的子结点,也就意味着u绝对不和这个DAG联通,那u的入度为0(U肯定也是根点),就要收买u,所以收买所有根点是可以抓整个DAG的间谍的。

所以,当根点无法被收买的时候,那无解,统计一下无法被收买的强连通分量(也就是根点)之中最小的点,然后输出即可

或者,有解,那么就统计根点的最小花费,加起来就OK。

不需要DFS/BFS。统计的时候可以玩枚举(强连通分量不会太多……)


#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;
//建图
struct edge
{
	int pre,to;
} edge[8010]={0};
int pointer[3010]={0},fx,tx,edgenum/*边数*/,nodenum/*点数*/,canbuynum/*带权点数*/;
//Tarjan算法 
int dfn[3010]={0},low[3010]={0},tstamp=0/*时间戳*/,header=0/*栈指针*/,stack[10010]={0}/*手动栈*/;
bool init[3010]={false};
//记录强连通分量以及缩点图 
int numbers[3010]={0}/*点属于哪个分量*/,counter=0/*记录强连通分量个数*/,/*缩点图*/inw[3010]={0},prex2;
int moneyx[3010]/*点权记录*/,cache1,cache4;//上面两个缓存是读点权的 
//答案处理 
long long minimoney,ans=0;//每个分量最小花费和总答案 
bool flag=false,flag2=false;//flag2是判断有没有一个缩点是无法被收买的 flag记录枚举过程中的点 
int mininode=10000,mininode2=10000;//没法收买的最小的点(第一个是枚举过程中的缓存) 
//链式前向星建图 
void Insert(int at,int fromx,int tox)
{
	edge[at].pre=pointer[fromx];
	edge[at].to=tox;
	pointer[fromx]=at;
	return;
}
//Tarjan模板 
void tarjan(int u)
{
	int cache2,cache3,prex;
	dfn[u]=low[u]=++tstamp;
	stack[++header]=u; init[u]=true;
	prex=pointer[u];
	while (prex>0)
	{
		cache2=edge[prex].to;
		if (dfn[cache2]==0)
		{
			tarjan(cache2);
			low[u]=min(low[u],low[cache2]);
		}
		else if (init[cache2]==true) low[u]=min(low[u],dfn[cache2]);
		prex=edge[prex].pre;
	}
	if (low[u]==dfn[u])
	{
		counter++;
		do
		{
			cache3=stack[header];
			header--;
			numbers[cache3]=counter;
			init[cache3]=false;
		} while (cache3!=u);
	}
	return;
}

void Shrink()
{
	for (int i=1;i<=nodenum;i++)
	{
		prex2=pointer[i];
		while (prex2>0)
		{
			if (numbers[i]!=numbers[edge[prex2].to])//一条边连接的两个点如果不处于一个强连通分量之中 
			{
				inw[numbers[edge[prex2].to]]++;//增加入度和出度 
			}
			prex2=edge[prex2].pre;//链式前向星…… 
		}
	}
	return;
}

void getminimoney(int u)
{
	minimoney=100000; flag2=false; mininode=10000;//初始化 
	for (int j=1;j<=nodenum;j++)
	{
		//cout<<"点 "<<j<<" 属于缩点 "<<numbers[j]<<" Money= "<<moneyx[j]<<endl;
		if (numbers[j]==u) //枚举到点j,j属于分量u 
		{
			if (moneyx[j]==-1 && mininode>j) mininode=j;//记录没法被收买的最小点 
			else if (moneyx[j]!=-1)//如果可以被收买 
			{
				if (minimoney>moneyx[j]) minimoney=moneyx[j];//记录花费的最小金额 
				flag2=true;//这个强连通分量可以被收买 
			}
		}
	}
	return;
}

int main()
{
	//读入数据 
	scanf("%d%d",&nodenum,&canbuynum);
	for (int i=0;i<=nodenum;i++) moneyx[i]=-1;
	for (int i=1;i<=canbuynum;i++)
	{
		scanf("%d%d",&cache1,&cache4);
		moneyx[cache1]=cache4;
	}
	scanf("%d",&edgenum);
	for (int i=1;i<=edgenum;i++)
	{
		scanf("%d%d",&fx,&tx);
		Insert(i,fx,tx);
	}
	//Tarjan求强连通分量 
	for (int i=1;i<=nodenum;i++) if (numbers[i]==0) tarjan(i);
	//缩点 
	Shrink(); 
	
	for (int i=1;i<=counter;i++) if (inw[i]==0)
	{
		//cout<<"缩点 "<<i<<" 入度为0"<<endl;
		getminimoney(i);
		if (flag2==false)
		{
			//cout<<"缩点发现无法收买的根节点!根节点="<<i<<endl;
			flag=true;//缩点发现无法收买的根节点
			if (mininode2>mininode) mininode2=mininode;//记录最小点 
		}
		else ans+=minimoney;
		//cout<<"强连通分量 "<<i<<" 最小花费= "<<minimoney<<endl;
		//cout<<"ans= "<<ans<<endl<<endl;
	}
	if (flag==true) printf("NO\n%d",mininode2);
	else printf("YES\n%d",ans);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值