今天考试像中了毒一样,第二题能A忘记换行,第一题编译错误(虽然成功了也没分...),又是小菜鸡成功爆0的一天。
总结一下今天的第三题 HNOI2009最小圈 二分+dfs版spfa判断负环
这道题自己做的时候压根没有想到用二分来实现。看到环直接想到了tarjan,还是太菜了。然鹅tarjan只能完成缩点,并且一缩就缩走一个大环,对于这道题而言,要考虑小环以及要算环权,并不清楚能不能实现(作为蒟蒻当然是不会的)。
根据神佬的指点,可以这样来考虑。
当我们拿到这个题目,图上的问题,这时候我们可以想到最短路算法,但是最短路算法是无后效性的,即后面的操作不会影响到前面的最优性。但是这道题是有后效性的,选了当前最优方案后整体不一定为最优方案。我们想到拓扑将不在环上的点去掉,for循环扫环,算答案后去掉,但是我们又想到有环可能会重用一条边,这样缩去一个环后也可能会去掉另一个环的某条边。想到用dp的话不好定义状态,n为3000,开二维空间勉强够,三维GG,时间复杂度也不好估计……这时候我们发现我们很难从所有解中直接找出最优解,我们靠神秘力量(多做题可以积攒神秘力量)思考到我们可以考虑将其转化为判断可行性问题,由从众多解中直接求得最优转化为求所有解中某些可行解。考虑用随机化或者二分来做。这时候我们可以思考二分答案的方法,二分答案的重点在于如何判断该答案的可行性。结合题目中的环的最小平均值,我们考虑到一个环的每条边权值-该环平均权值后成为一个“0环”。dfs可以用来判断环,因为深搜是一路搜下去,若我们搜到了他的某一个祖先(若该图为无向图,则父亲不包括在内),则有环。用spfa的思想,当这个点可以更新其祖先的dis数组时,环为负环。这时候我们的思路大致就清晰了。代码比较简单实现,需要注意题目中的一个点。“有一个点可以到达其他所有点”,这句话说明我们不能随意以一个点作为dfs的起点,因为他不一定能访问到负环,这时候我们 dfs n次,当有一次成功后可break掉。
代码自行理解,应该不是太难。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 int n,m,cnt,flag; 6 int head[10010]; 7 bool vis[10010]; 8 double dis[10010]; 9 struct node{ 10 int to,next;double v; 11 }edge[10010]; 12 int read() 13 { 14 int x=0,w=1;char ch=getchar(); 15 while(ch>‘9‘||ch<‘0‘) {if(ch==‘-‘)w=-1;ch=getchar();} 16 while(ch>=‘0‘&&ch<=‘9‘) x=(x<<3)+(x<<1)+ch-‘0‘,ch=getchar(); 17 return x*w; 18 } 19 void add(int x,int y,double v) 20 { 21 cnt++; 22 edge[cnt].to=y; 23 edge[cnt].next=head[x]; 24 edge[cnt].v=v; 25 head[x]=cnt; 26 } 27 void spfa(int k,double jian) 28 { 29 vis[k]=1; 30 int v; 31 for(int i=head[k];i;i=edge[i].next) 32 { 33 v=edge[i].to; 34 if(dis[v]>dis[k]+edge[i].v-jian) 35 { 36 if(vis[v]) 37 { 38 flag=1; 39 return; 40 } 41 dis[v]=dis[k]+edge[i].v-jian; 42 spfa(v,jian); 43 if(flag) return; 44 } 45 } 46 vis[k]=0; 47 } 48 bool check(double mid) 49 { 50 flag=0; 51 memset(dis,0x3f,sizeof(dis)); //这里注意进入dfs时应当所有点的dis值一样,0,0x3f……都可以。这样才能保证搜到了的是负环。(仔细想想这里,若如spfa那样第一个点为0,则有可能找到的环不是负环。本人表述能力有限,尽量理解) 52 memset(vis,0,sizeof(vis)); 53 for(int i=1;i<=n;i++) 54 { 55 spfa(i,mid); 56 if(flag) return 1; 57 } 58 return 0; 59 } 60 int main() 61 { 62 int x,y; 63 double z; 64 n=read();m=read(); 65 for(int i=1;i<=m;i++) 66 { 67 x=read();y=read(); 68 scanf("%lf",&z); 69 add(x,y,z); 70 } 71 double l=-10000000,r=10000000,mid; 72 while(r-l>1e-10) 73 { 74 mid=(l+r)/2; 75 if(check(mid)) 76 { 77 r=mid; 78 } 79 else 80 { 81 l=mid; 82 } 83 } 84 printf("%.8lf",mid); 85 return 0; 86 }