【洛谷2017年3月月赛】Round1第四题-环套树森林DFS+贪心
比赛地址:洛谷2017年3月月赛Round1
这个比赛也是参加来玩的......结果第一题这是什么鬼!剩下的正经的三题里,前两题都挺简单的不说了,主要是这个第四题很有意思。
题目的大意就是一个有向图,每个点入度都为1,每条边有边权,要修改这条边的指向要边权这么多的花费,问修改一些边(还要满足原图的性质)后使图上任意两点间相互可达的最小花费。
我们很快就能得知题目要修改成的图肯定是一个环。我们知道环上的点入度为1,出度也为1,入度为1题目中已经限定了,我们就要删掉一些边使得每个点出度也为1。分析原图的性质得知,原图就是一个环套树森林,环套树就是往树里添加一条边使得有一个环,而题目中并没有说原图是连通的,所以原图就是一个环套树森林。再分析,如果一个点的出度>1,那么在这些出边中必定要修改一些边使得出度=1,因为一个点的出边不可能和其他点的出边重合,所以我们可以直接贪心:只保留最长的边,其他边都要修改。可以证明这样得到的花费是最小的。
然而还有一个问题,如果按照上面的方法删除要修改的边,可能还有剩下的环,这是不符合要求的。那么要怎么办呢?答案是:对于每个非环上的点,就直接用上面的方法贪心。对于环上的点,我们就要这样做:我们知道每个环上至少要删除一条边才能满足要求,那么我们就贪心求出要删除哪一条环边。要注意的是,这里可不是简单地删除环里的最短边就行了,因为还有可能在用上面的方法删除环上点的出边时,能一举两得地删掉一条环边,这时这条环边可能不是环上最短的,如果简单删除环上的最短边则就可能多此一举。
正确的方法是:分别考虑每一个环,记录环上每一个点出边中环边的边权与非环边中边权的最大值的差值,这个差值有什么意义呢?就是指保留非环边中的最长边比保留环边要多的花费值,于是贪心找到这个差值最小的点,在这个点只保留非环边的最长边,其他边(包括环边)删掉,而对于其他点就按照以上最简单的贪心方法就可以找到满足要求的最优解了。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define inf 1000000007
using namespace std;
int n,val[100010],first[100010]={0},tot=0;
int vis[100010]={0},p=0,path[100010],start,end;
long long ans=0;
bool loop[100010]={0};
struct edge {int v,next;long long d;} e[100010];
void insert(int a,int b,int c)
{
e[++tot].v=b,e[tot].d=c,e[tot].next=first[a],first[a]=tot;
}
bool findloop(int v,int f)
{
vis[v]=p;path[f]=v;
for(int i=first[v];i;i=e[i].next)
{
if (!vis[e[i].v])
{
if (findloop(e[i].v,f+1)) return 1;
}
else if (vis[e[i].v]==p)
{
int j=f;
while(path[j]!=e[i].v)
{
loop[path[j]]=1;
j--;
}
loop[path[j]]=1;
start=j,end=f;
return 1;
}
}
return 0;
}
long long dfs(int v)
{
long long sum=0,mx=0;
for(int i=first[v];i;i=e[i].next)
{
sum+=e[i].d;
mx=max(mx,e[i].d);
ans+=dfs(e[i].v);
}
return sum-mx;
}
int main()
{
scanf("%d",&n);
for(int i=1,a;i<=n;i++)
{
scanf("%d%d",&a,&val[i]);
insert(a,i,val[i]);
}
int last=1;
while(1)
{
for(;last<=n;last++)
if (!vis[last])
{
p++;
if (findloop(last,0))
{
last++;
break;
}
}
if (last>n) break;
if (end-start+1==n) break;
long long minx=inf,mini,mle,mmx;
for(int i=start;i<=end;i++)
{
long long mx=0,loope;
for(int j=first[path[i]];j;j=e[j].next)
{
if (!loop[e[j].v])
{
ans+=dfs(e[j].v);
mx=max(e[j].d,mx);
}
else loope=e[j].d;
ans+=e[j].d;
}
if (loope-mx<minx)
{
minx=loope-mx,mini=i;
mle=loope,mmx=mx;
}
}
for(int i=start;i<=end;i++)
{
if (mini==i)
ans-=mmx;
else
{
long long mx=0;
for(int j=first[path[i]];j;j=e[j].next)
{
mx=max(mx,e[j].d);
}
ans-=mx;
}
}
}
printf("%lld",ans);
return 0;
}