这道题太神了吧,从昨晚七点半做到今天下午两点.
我经历了以下折腾(以下内容可跳过):
- 一开始想的是用Tarjan缩点,然后跑最短路,通过路径染色,让一条路径上的点的权值等于起点(也就是可以被收买的美国间谍的编号),然后枚举每一个点,用它对应的路径,累加权值得到答案,如果有点的权值为正无穷,那么就说明有间谍无法收买,再扫一遍染色数组找出编号最小的点输出NO.
这种方式貌似可以,但是在跑最短路的时候,松弛操作会把权值更小的路径更新进来,但是这条最短路可能是无法确保跑向权值更大的点的前缀点的,所以说会导致权值反而更大.这种方法最高得了52分.
比如下面这张图:
1,2,3,4号点明显是个强联通分量,收买这个分量的代价为10.
5号点也是一个强连通分量,收买它的代价为20.
该图只有从5号点开始,才可以遍历完所有的点,总代价为20.
但是在跑最短路时,进入缩过点后的1,2,3,4节点后,会把这个点的权值松弛为10,但是这个10是无法跑完整个图的,这也导致总代价变为了30;
然后我甚至想缩点之后跑一边最小生成树,但是算了下这样做会超时.
之后我又想好了好久,和zxs大佬在中午恰饭的时候交流了下这道题,才想出来可以记录下所有入度为0的点,然后累加这些点的权值.这是因为入度为0的点是一定要被收买的,不然就无法遍历完全图,如果有间谍点无法被收买而且无法被其他间谍告发,就输出NO.
还有一些细节在代码里解释罢,各位在阅读我的变量名时只用看下划线后的部分.
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
using namespace std;
stack<int>s;
struct edge
{
int to,next;
}e[10010];
int OddToThePeoplesVolunteerArmy_n,GreatSoviet_p,TheSovietUnion_CntSize,PeoplesRupublic_TimeCnt,YpaForOurGreatMotherland_r,TheDefenderOfMoscow_size,TheInternationalMustCometrue_ans;
int head[10010],dfn[10010],low[10010],indu[10010],dis[10010],color[10010],Nodedis[10010];
bool flag[10010];
void EdgeAdd(int,int);
void Tarjan(int);
int main()
{
memset(head,-1,sizeof(head));
memset(Nodedis,0x3f,sizeof(Nodedis));
memset(dis,0x3f,sizeof(dis));
scanf("%d%d",&OddToThePeoplesVolunteerArmy_n,&GreatSoviet_p);
for(int _=1;_<=GreatSoviet_p;_++)
{
int id,USAIsRubbish;
scanf("%d%d",&id,&USAIsRubbish);
dis[id]=USAIsRubbish;
}
scanf("%d",&YpaForOurGreatMotherland_r);
for(int _=1;_<=YpaForOurGreatMotherland_r;_++)
{
int father,son;
scanf("%d%d",&father,&son);
EdgeAdd(father,son);
}
for(int _=1;_<=OddToThePeoplesVolunteerArmy_n;_++)
{
if(dfn[_]==0&&dis[_]!=0x3f3f3f3f)//把可以收买的美国间谍进入Tarjan缩点,这样缩出来的点才能够被遍历.
{
Tarjan(_);
}
}
for(int _=1;_<=OddToThePeoplesVolunteerArmy_n;_++)//缩完点后如果存在无法被收买,又无法被其它间谍指控的点,就说明无法收买所有间谍.
{
if(dfn[_]==0)
{
printf("NO\n%d\n",_);
return 0;
}
}
for(int _=1;_<=OddToThePeoplesVolunteerArmy_n;_++)
{
for(int __=head[_];__!=-1;__=e[__].next)
{
int to=e[__].to;
if(color[_]!=color[to])
{
indu[color[to]]++;//统计入度.
}
}
}
for(int _=1;_<=TheSovietUnion_CntSize;_++)
{
if(indu[_]==0)//累加必须收买的间谍的代价.
{
TheInternationalMustCometrue_ans+=Nodedis[_];
// printf("Nodedis:%d\n",Nodedis[_]);
}
}
printf("YES\n%d\n",TheInternationalMustCometrue_ans);
return 0;
}
void EdgeAdd(int from,int to)
{
e[++TheDefenderOfMoscow_size].to=to;
e[TheDefenderOfMoscow_size].next=head[from];
head[from]=TheDefenderOfMoscow_size;
}
void Tarjan(int FuckTrump_from)
{
dfn[FuckTrump_from]=low[FuckTrump_from]=++PeoplesRupublic_TimeCnt;
s.push(FuckTrump_from);
flag[FuckTrump_from]=true;
for(int _=head[FuckTrump_from];_!=-1;_=e[_].next)
{
int to=e[_].to;
if(dfn[to]==0)
{
Tarjan(to);
low[FuckTrump_from]=min(low[FuckTrump_from],low[to]);
}
else if(flag[to]==true)
{
low[FuckTrump_from]=min(low[FuckTrump_from],dfn[to]);
}
}
if(dfn[FuckTrump_from]==low[FuckTrump_from])
{
TheSovietUnion_CntSize++;
while(!s.empty())
{
int RedAmryIsTheStrongest_temp=s.top();
s.pop();
flag[RedAmryIsTheStrongest_temp]=false;
color[RedAmryIsTheStrongest_temp]=TheSovietUnion_CntSize;
Nodedis[TheSovietUnion_CntSize]=min(Nodedis[TheSovietUnion_CntSize],dis[RedAmryIsTheStrongest_temp]);//缩点后的点的代价为原来的环的代价中最小的那个代价.
color[RedAmryIsTheStrongest_temp]=TheSovietUnion_CntSize;//染色.
if(RedAmryIsTheStrongest_temp==FuckTrump_from)break;
}
}
}