CodeVS&Luogu 间谍网络

来源:互联网 发布:mysql密码策略设置 编辑:程序博客网 时间:2024/05/01 16:34

移步到新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;}