题面
题解
卡常好题…
考场上乱搞了一个贪心居然骗到了95分。
考后和标程对拍了4000组数据才找出来哪里错了…
智商堪忧…
由于
n
≤
300
n≤300
n≤300可以直接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
<
<
k
)
ans=ans + (1<<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;
}