题目背景:
幻想乡的亡灵公主,西行寺幽幽子,在幻想乡很受欢迎,经常有妖怪来拜访她,但是幽
幽子并不喜欢被打扰,她希望从白玉楼出发,散步之后再回到白玉楼,同时路上遇到的妖怪越少越好(有趣的是道路两边的妖怪数量并不相同,分别从两个方向经过同一条道路遇到的妖怪数量是不同的)。当然,作为冥界的公主,她是不会重复经过同一条道路的。
问题描述:
给定一个有 n 个点 m 条无向边的图,每条无向边最多只能经过一次。
对于边(ui, vi), 从 ui 到 vi 的代价为 ai,从 vi 到 ui 的代价为 bi,其中 ai 和 bi 不一定相等。 求一个包含 1 号点的有向环,使得环上代价之和最小。(保证图中没有重边和自环。)
输入格式:
第一行两个个正整数 n,m,点数和边数。
接下来 m 行,每行四个正整数,ui,vi,ai,bi。
从 ui 到 vi 的代价为 ai,从 vi 到 ui 的代价为 bi 。
输出格式:
输出一行,一个整数,如果有解输出最小代价,否则输出”-1”(不含引号)。
样例数据 1:
zaw.in
3 3
1 2 4 3
2 3 4 2
1 3 1 1
zaw.out
6
样例数据 2:
zaw.in
3 2
1 2 12 5
3 2 10 5
zaw.out
-1
数据范围:
对于前 15% 的数据,3 <= n <= 20, 3 <= m <= 20
对于前 30% 的数据,3 <= n <= 150, 3 <= m <= 2500
对于前 70% 的数据,3 <= n <= 5000, 3 <= m <= 10^4
对于 100% 的数据, 3 <= n <= 3*10^4, 3 <= m <= 10^5
1<=ui, vi <= n,1 <= ai, bi <= 10^4。
保证图中没有重边,即不存在 i <> j,使得 ui = uj,vi = vj。
保证图中没有自环,即 ui <> vi。
【题解】
算法一:
搜索
期望得分: 15 分
算法二:
启发式搜索
期望得分: 15 ~ 100 分
算法三:
枚举从 1 号点出发到达的第一个点 x 和回到 1 号点之前经过的的最后一个点 y。
那么有向环 1 -> x -> y -> 1 的代价为 w(1, x) + dist(x, y) + w(y, 1) 。
其中 dist(x, y)为不经过 1 号点,从 x 到 y 的代价。显然可以用最短路解决。
时间复杂度: O(n * n * SPFA(n,m)) / O(n^3)
期望得分: 30 分
算法四:
我们发现算法三的瓶颈在于求 dist(x, y),其实我们只要求 n 次单源最短路就可以了。
时间复杂度: O(n * SPFA(n,m)) / O(n * n * log n)
期望得分: 70 分
算法五:
我们发现算法四的瓶颈在于枚举 x 和 y。
事实上,我们可以把除 1 号点以外的点分成两个集合 ,X 和 Y。
然后通过一次最短路求出有向环 1 -> x ∈ X -> y ∈ Y -> 1 的最小代价。
那么按二进制位分组,考虑二进制下第 i 位。
1.第 i 位为 0 的点划分到 X 集合,第 i 位为 1 的点划分到 Y 集合,然后跑最短路更新答案。
2.第 i 位为 1 的点划分到 X 集合,第 i 位为 0 的点划分到 Y 集合,然后跑最短路更新答案。
我们知道只要任意点对都至少被分别划分到两个集合中一次,就可以得到答案。
而两个数至少有一个二进制位不相同,所以任意点对都至少被划分一次。
时间复杂度: O(log n * SPFA(n, m)) / O(n * log n * log n)
期望得分:100 分
不过经试验,出题人题解的满分算法会TLE到2.33s,但出题人的算法4反而在删掉没用东西后能AC。。。
找到节点1所有的相邻节点,dis[t]初值设为a[x].w,然后跑一遍t->1的SPFA,加上最优性剪枝即可。
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define fp(i,a,b) for(int i=a;i<=b;i++)
#define fq(i,a,b) for(int i=a;i>=b;i--)
#define il inline
#define ll long long
using namespace std;
const int maxN=30005;
const int maxM=100001*4;
const int inf=214748364;
struct Edge
{
int u,v,w,next;
}e[maxM];
int n,m,ans=inf,cnt,head,dis[maxN],q[maxM*10],tail,h[maxN];
bool inq[maxN];
il int gi()
{
int x=0;
short int t=1;
char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
void add(int u,int v,int w)
{
e[++cnt]=(Edge){u,v,w,h[u]};h[u]=cnt;
}
il void spfa(int st,int initw)//模板
{
memset(dis,127,sizeof(dis));
memset(inq,0,sizeof(inq));
dis[st]=initw;
q[tail++]=st;
inq[st]=1;
while(head<tail)
{
int u=q[head++];
inq[u]=0;
if(dis[u]>=ans)
{
inq[u]=0;
continue;
}
if(u==1)
{
ans=dis[u];
inq[u]=0;
continue;
}//最优性剪枝
for(int i=h[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(u==st&&v==1) continue;
if(dis[u]+e[i].w<dis[v])
{
dis[v]=dis[u]+e[i].w;
if(!inq[v])
{
q[tail++]=v;
inq[v]=1;
}
}
}
}
}
int main()
{
freopen("zaw.in","r",stdin);
freopen("zaw.out","w",stdout);
memset(h,-1,sizeof(h));
n=gi();m=gi();
fp(i,1,m)
{
int u=gi(),v=gi(),a=gi(),b=gi();
add(u,v,a);
add(v,u,b);
}
ans=inf;
for(int i=h[1];i!=-1;i=e[i].next)
spfa(e[i].v,e[i].w);
if(ans==inf) printf("-1\n");
else printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
return 0;
}
最后,把大佬的程序(也是跑的最快的)放在这里,看到时候是否能看懂。
// MADE BY QT666
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=500050;
const int Inf=19260817;
int cnt=1,n,m,head[N],to[N],nxt[N],c[N],in[N],vis[N],ans=Inf,vis2[N];
int W[N];
struct Data{
int fir,sec,ffrom,sfrom;
}dis[N];
struct data{
int x,fa,from;
}q[N*2];
void lnk(int x,int y,int v,int z){
to[++cnt]=y,c[cnt]=v,nxt[cnt]=head[x],head[x]=cnt;
to[++cnt]=x,c[cnt]=z,nxt[cnt]=head[y],head[y]=cnt;
}
void spfa(){
int t=0,sum=1;q[0].x=1;dis[1].fir=dis[1].sec=0;
while(t<sum){
data now=q[t++];
for(int i=head[now.x];i;i=nxt[i]){
if(i!=(now.fa^1)||now.fa==0){
int y=to[i],flag=0;
if(now.x!=1){
if(dis[now.x].fir+c[i]<dis[y].fir){
if(dis[y].fir<dis[now.x].sec+c[i]){
if(dis[y].ffrom!=dis[now.x].ffrom){
dis[y].sec=dis[y].fir;dis[y].sfrom=dis[y].ffrom;
dis[y].fir=dis[now.x].fir+c[i];dis[y].ffrom=dis[now.x].ffrom;
flag=1;
}
else{
if(dis[y].sec<dis[now.x].sec){
dis[y].fir=dis[now.x].fir+c[i];
dis[y].ffrom=dis[now.x].ffrom;flag=1;
}
else{
if(dis[y].ffrom!=dis[now.x].sfrom){
dis[y].fir=dis[now.x].fir+c[i];
dis[y].ffrom=dis[now.x].ffrom;
dis[y].sec=dis[now.x].sec+c[i];
dis[y].sfrom=dis[now.x].sfrom;flag=1;
}
}
}
}
else{
dis[y].fir=dis[now.x].fir+c[i];
dis[y].ffrom=dis[now.x].ffrom;
dis[y].sec=dis[now.x].sec+c[i];
dis[y].sfrom=dis[now.x].sfrom;
}
}
else {
if(dis[y].sec<dis[now.x].sec+c[i]&&dis[y].sec>dis[now.x].fir+c[i]){
if(dis[y].ffrom!=dis[now.x].ffrom){
dis[y].sec=dis[now.x].fir+c[i];
dis[y].sfrom=dis[now.x].ffrom;flag=1;
}
}
else if(dis[y].sec>dis[now.x].sec+c[i]){
if(dis[y].ffrom!=dis[now.x].ffrom){
dis[y].sec=dis[now.x].fir+c[i];
dis[y].sfrom=dis[now.x].ffrom;flag=1;
}
else if(dis[y].ffrom!=dis[now.x].sfrom){
dis[y].sec=dis[now.x].sec+c[i];
dis[y].sfrom=dis[now.x].sfrom;flag=1;
}
}
}
}
if(flag||now.x==1) q[sum++]=(data){y,i};
}
}
}
}
int main(){
freopen("zaw.in","r",stdin);
freopen("zaw.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y,v,z;scanf("%d%d%d%d",&x,&y,&v,&z);
lnk(x,y,v,z);
}
for(int i=2;i<=n;i++) dis[i].fir=dis[i].sec=Inf;
for(int i=head[1];i;i=nxt[i]){
if(dis[to[i]].fir==Inf){
dis[to[i]].fir=c[i];
dis[to[i]].ffrom=i;
}
else{
if(dis[to[i]].fir>c[i]){
dis[to[i]].sec=dis[to[i]].fir;
dis[to[i]].sfrom=dis[to[i]].ffrom;
dis[to[i]].fir=c[i];
dis[to[i]].ffrom=i;
}
else{
if(dis[i].sec>c[i]){
dis[to[i]].sec=c[i];
dis[to[i]].sfrom=i;
}
}
}
}
spfa();
for(int i=head[1];i;i=nxt[i]){
int y=to[i];
if(dis[y].ffrom!=i) ans=min(ans,dis[y].fir+c[i^1]);
else ans=min(ans,dis[y].sec+c[i^1]);
}
if(ans==Inf) puts("-1");
else printf("%d",ans);
}