[2019寒假集训day4]负环(倍增)

题面

在这里插入图片描述

题解

卡常好题…
考场上乱搞了一个贪心居然骗到了95分。
考后和标程对拍了4000组数据才找出来哪里错了…
智商堪忧…
由于 n ≤ 300 n≤300 n300可以直接Floyd。
然而本题要求是最小负环,所以要限制长度。
直接限制复杂度就变 O ( n 4 ) O(n^4) O(n4)了。
因此考虑倍增的思想。
d i s [ i ] [ j ] [ k ] dis[i][j][k] dis[i][j][k]表示从 i i i j j j长度为 ≤ 2 k ≤2^k 2k时的最短路径。
一定要注意是小于等于。
预处理好dis之后,从大到小枚举 k k k
g [ i ] [ j ] g[i][j] g[i][j]为上一次松弛得到的图, f [ i ] [ j ] f[i][j] f[i][j]为这一次松弛得到的图。
本次将 d i s [ i ] [ j ] [ k ] dis[i][j][k] dis[i][j][k]全部加入松弛。具体来说是:
f [ i ] [ j ] = m i n ( f [ i ] [ j ] , g [ i ] [ p ] + d i s [ p ] [ j ] [ k ] ) f[i][j]=min(f[i][j],g[i][p]+dis[p][j][k]) f[i][j]=min(f[i][j],g[i][p]+dis[p][j][k])
需要注意的是在 i i i j j j的路径上只会新加入最多一条这样的边。
如果本次松弛发现已经出现负环,则说明可以缩小限制,故让 f [ i ] [ j ] = g [ i ] [ j ] f[i][j]=g[i][j] f[i][j]=g[i][j]
如果本次松弛没有负环,说明还要向后加大限制,故让 g [ i ] [ j ] = f [ i ] [ j ] g[i][j]=f[i][j] g[i][j]=f[i][j],同时可以让答案 a n s = a n s + ( 1 &lt; &lt; k ) ans=ans + (1&lt;&lt;k) ans=ans+(1<<k)
更通俗的来讲,就是每次在一条 i i i j j j的路径上加入一条长度 ≤ 2 k ≤2^k 2k的路径,如果还有负环,说明这个最小负环还能再缩小限制,故不保存本次松弛得到的图,否则就说明它必须要一条长度 ≤ 2 k ≤2^k 2k的边,并将其计入答案。
本质上是一种二分。
复杂度: O ( n 3 log ⁡ n ) O(n^3\log n) O(n3logn)
考试的时候还不相信可以1s过,卡卡常就好了…

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
#define MAXN 300
#define MAXM 90000
#define LL long long
#define INF 500000000
int n,m,u,v,w;
int d[9][MAXN+5][MAXN+5];
int f[MAXN+5][MAXN+5];
int g[MAXN+5][MAXN+5];
int main()
{
	freopen("cycle.in","r",stdin);
	freopen("cycle.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		if(i!=j)d[0][i][j]=INF;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&u,&v,&w);
		d[0][u][v]=w;
	}
	for(int t=1;t<=8;t++)
	{
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
			if(i!=j)d[t][i][j]=INF;
		for(int k=1;k<=n;k++)
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++)
				d[t][i][j]=min(d[t][i][j],d[t-1][i][k]+d[t-1][k][j]);
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		if(i!=j)f[i][j]=INF;
	int ans=0;
	for(int t=8;t>=0;t--)
	{
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
			g[i][j]=f[i][j];
		for(int k=1;k<=n;k++)
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++)
				f[i][j]=min(f[i][j],g[i][k]+d[t][k][j]);
		bool flag=0;
		for(int i=1;i<=n;i++)if(f[i][i]<0){flag=1;break;}
		if(flag)
		{
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++)
				f[i][j]=g[i][j];
		}
		else ans^=1<<t;
	}
	ans++;
	if(ans>n)printf("0\n");
	else printf("%d",ans);
}

附上原来的贪心代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
#define MAXN 300
#define MAXM 90000
#define LL long long
#define INF 100000000000000000
int n,m;
LL dis[MAXN+5][MAXN+5],L[MAXN+5][MAXN+5],ans;
int u[MAXM+5],v[MAXM+5],w[MAXM+5];
int main()
{
	//freopen("cycle.in","r",stdin);
	//freopen("cycle.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		dis[i][j]=INF,L[i][j]=INF;
	ans=INF;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&u[i],&v[i],&w[i]);
		dis[u[i]][v[i]]=w[i];
		L[u[i]][v[i]]=1;
		//if(dis[u[i]][v[i]]+dis[v[i]][u[i]]<0)printf("%d %d\n",dis[u[i]][v[i]],dis[v[i]][u[i]]);
	}
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
			if(dis[i][j]>0)
			{
				if(dis[i][j]>dis[i][k]+dis[k][j])dis[i][j]=dis[i][k]+dis[k][j],L[i][j]=L[i][k]+L[k][j];
				else if(dis[i][j]==dis[i][k]+dis[k][j])L[i][j]=min(L[i][j],L[i][k]+L[k][j]);
			}
			else
			{
				if(dis[i][k]+dis[k][j]<0&&L[i][j]>L[i][k]+L[k][j])dis[i][j]=dis[i][k]+dis[k][j],L[i][j]=L[i][k]+L[k][j];
				else if(dis[i][k]+dis[k][j]<0&&L[i][j]==L[i][k]+L[k][j])dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
			}
	for(int i=1;i<=n;i++)
	if(dis[i][i]<0)ans=min(ans,L[i][i]);
	if(ans==INF)printf("0\n");
	else printf("%lld",ans);
}

生成数据代码:
虽然大多数生成出来的数据都是0和3…

//pai for problem cycle
#include<iostream>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#include<cstdio>
using namespace std;
#define MAXN 300
#define MAXM 90000
#define LL long long
#define INF 10000
#define MOD 1000000000
int flag[300][300];
LL cal(LL a,LL b)
{
	LL res=0,k=a;
	for(int i=1;i<=b;i++)
	res=(res+a)%MOD,k=k*(LL)rand()%MOD,a=(a+k)%MOD;
	return res;
}
LL rand_range(LL l,LL r)
{
	LL val=cal(rand()*rand()%INF+1,rand()%10+1);
	return val%(r-l+1)+l;
}
int main()
{
    srand(time(NULL));
   //freopen("cycle1.in","w",stdout);
    int n=rand_range(50,300),m=rand_range(100,n*(n-1));
    printf("%d %d\n",n,m);
    for(int i=1;i<=m;i++)
    {
        int u=rand_range(1,n),v=rand_range(1,n);
		while(u==v||flag[u][v])u=rand_range(1,n),v=rand_range(1,n);
		flag[u][v]=1;
		printf("%d %d ",u,v);
		int del=rand_range(1,100);
		if(flag[v][u]==-1)printf("%d\n",rand_range(7000,10000));
		else if(del<=1)printf("%d\n",9999);
		else if(del<=3){flag[u][v]=-1;printf("%d\n",-10);}
		else if(del<=16)printf("%d\n",INF);
		else if(del<=40){flag[u][v]=-1;printf("%d\n",rand_range(-4000,-1000));}
		else printf("%d\n",rand_range(7000,10000));
    }
}

对拍:

#include<iostream>  
#include<windows.h>  
using namespace std;  
int main()  
{  
    int testdata=1100;
    while(testdata)  
    {
    	if(testdata>1000)printf("Running on pretest %d\n",1101-testdata);
		else printf("Running on standard test %d\n",1001-testdata);
        testdata--;
        system("data.exe > cycle.in");system("forces.exe < cycle.in > forces.txt");  
         system("cycle.exe < cycle.in > cycle.txt");  
        if(system("fc forces.txt cycle.txt"))   break;  
    }
	if(testdata==0) cout<<"Accepted"<<endl;  
    else if(testdata>1000)printf("Wrong Answer on pretest %d\n",1100-testdata);
	else printf("Wrong Answer on standard test %d\n",1000-testdata); 
    system("pause");  
    return 0;  
}  
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值