题意
给你一个无向图,并告诉你有k条边是一定要走的,有m条边是可走可不走。然后问你从1号点出发,经过所有的k条边,最后回到1号点的最小代价
前言
这题我做了很久,好像差不多有一个下午加晚上吧。。
一开始找到Claris的题解。。不是很敢弄。。
然后发现dis里面似乎有新大陆,于是又弄了很久。。但是一直没有弄出什么东西来
最后还是回去了Claris的做法
然后弄懂了之后,还是不是很敢再打一次,因为上一次大部分都是参考Claris的。
我就来认真地口胡一篇博客来假装自己会了吧。。
题解
我们分成两种情况讨论,K
≤
15,和k
为什么要分这两种情况
因为我们可以发现,当k
>
15的时候,由于n的最大值只有13,所以他至多只会有7个联通块,达到最大联通块的方案是,6个点里面互相连边,然后这里是15条边,此时有 剩下的7个点+1个联通块=8个联通块,然后无论你下一个加到哪里,比减少一个联通块,于是就是7个了。。
我们考虑最后的答案,如果所有包含关键边的联通块都和1号点属于联通块的话,那么至少说明,这个方案有可能可行的
但是还是有一种不存在回路的情况。。
根据无向图是否存在欧拉回路的判定方法,如果所有点的度数都是偶数,那么就存在一条欧拉回路。。
于是我们可以断定,如果在最后的状态里面,如果该状态的关键边与1号点连通且度数均为偶数,那么就是可行的
K≤ 15
这个很好办,考虑到k很小,我们不妨状压一下每一条关键边的经过状态,然后搭上当前在哪个点,就可以出来一个最短路了,f[i][j]表示当前在i号点,经过状态为j的最小代价
时间复杂度
O(n∗215)
然后用dij跑的话应该还是多一个log的
代码实现:
int f[N][N][2],g[N][N],d[N][(1<<LIM)];
priority_queue<PI,vector<PI>,greater<PI> >q;
void ext (int x,int y,int z)//看看之前是否存在这个状态
//x现在在哪一个点 走的那些边状态是什么
{
if (d[x][y]<=z) return ;
q.push(PI(d[x][y]=z,P(x,y)));
}
void solve ()
{
for (int u=0;u<n;u++)
for (int i=0;i<=n;i++)
f[u][i][0]=-1;
for (int u=0;u<n;u++)
for (int i=0;i<n;i++)
g[u][i]=MAX;
for (int u=0;u<k;u++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
x--;y--;
f[x][y][0]=f[y][x][0]=u;
f[x][y][1]=f[y][x][1]=z;
}
scanf("%d",&m);
while (m--)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
x--;y--;
up(g[x][y],z);up(g[y][x],z);
}
for (int u=0;u<n;u++)
for (int i=0;i<(1<<k);i++)
d[u][i]=MAX;
ext(0,0,0);
while (!q.empty())
{
PI t=q.top();q.pop();
int x=t.second.first,y=t.second.second,z=t.first;
if (z>d[x][y]) continue;
for (int u=0;u<n;u++)
{
if (f[x][u][0]!=-1)//有这条边
ext(u,y|(1<<f[x][u][0]),z+f[x][u][1]);
ext(u,y,z+g[x][u]);
}
}
printf("%d\n",d[0][(1<<k)-1]);
}
K>15
这种情况就不是特别好办了。。
但是读了第一部分的人都知道,此时我们用尽各种奇技淫巧,可以吧一个图缩成7个联通块。而7个联通块的连接状态,其实就是贝尔数,这个如果有研究过数论的就都知道。感觉在看斯特林数的时候经常可以看到这个(暴露智商)。
Bell(7)
是只有877个状态的,于是我们就可以位压了。。
不妨DPf[i][j][k]表示用了前i条边,然后连通状态为j,每个点的奇偶状态为k的最小代价是什么。。
然后DP一下就就可以了
同时为了处理方便,我们可以引入一个g数组,和q数组
q预先吧所有状态都处理出来,放在里面, 也就是上面的j不是一个状态,只是状态的一个编号而已
然后g表示某一个状态加上某一条边会到达什么状态,这就可以实现了
代码实现:
int a[N];//前期:作为并查集 后期:辅助数组
int dp[2][1<<N];//滚动 前k条边 然后是每个点的奇偶性的 最小代价
bool must[N];//这个是不是一个必要点
int g[M][N][N];//这个状态加上这条边是什么状态
int e[N][N];//这两个点之间的最优距离
char T;//时间轴
int w[2][M][1<<N];//第n条边,这个连通状态,每个点奇偶性为0 滚动
char v[2][M][1<<N];//时间轴
short s[2][M][1<<N],cnt[2][M];//状态 对于这种连通状态有多少种情况
map<LL,int>id;
int o=0,h=0,t=0;
LL q[M];
void Merge (int x,int y)//这两个点连在一起
{
x=a[x];y=a[y];
for (int u=0;u<n;u++)
if (a[u]==x)
a[u]=y;
}
LL encode ()//吧当前的a压进t
{
//具体思路:就是把每一块的祖先都标出来
//然后对于每一个块,用他最小编号的点将他编号,这样的话就不会有两种重复的情况
//然后对于每一个点,用他所在的编号就可以知道他的联通块人了
int m=0;
LL t=0;
int v[N];
for (int u=0;u<n;u++) v[a[u]]=-1;
for (int u=0;u<n;u++)
{
if (v[a[u]]<0) v[a[u]]=m++;
t=t<<4|v[a[u]];
}
return t;
}
void decode (LL f)//将这个状态变回a里面
{
for (int u=n-1;u>=0;u--)
a[u]=(f&15),f>>=4;
}
int ext (LL x)
{
//看看是否存在x这个状态,并给他标号
if (id[x]!=0) return id[x];
id[x]=++t;
q[id[x]]=x;
return id[x];
}
void clr ()
{
T++;
for (int u=1;u<=t;u++) cnt[o^1][u]=0;
}
void add (int x,int y,int z)
{
if (z>=MAX) return ;
if (v[o^1][x][y]<T)
{
v[o^1][x][y]=T;
w[o^1][x][y]=z;
s[o^1][x][cnt[o^1][x]++]=y;
return ;
}
up(w[o^1][x][y],z);
}
bool check(LL f)
{
decode(f);
for(int i=0; i<n; i++) if(must[i]==true&&a[i]!=a[0])return 0;
return 1;
}
void solve ()
{
for (int u=0;u<n;u++) a[u]=u;
for (int u=1;u<(1<<n);u++) dp[0][u]=MAX;
memset(must,false,sizeof(must));
while (k--)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
x--;y--;
must[x]=true;must[y]=true;
Merge(x,y);
for (int u=0;u<(1<<n);u++) dp[o^1][u]=MAX;
for (int u=0;u<(1<<n);u++)
if (dp[o][u]<MAX)
{
up(dp[o^1][u^(1<<x)^(1<<y)],dp[o][u]+z);
up(dp[o^1][u],dp[o][u]+z+z);
}
o^=1;
}
decode(encode());
h=1;
ext(encode());
while (h<=t)//这个就是建立那877个状态
{
LL x=q[h];
for (int u=0;u<n;u++)
for (int i=0;i<n;i++)
{
decode(x);
Merge(u,i);
g[h][u][i]=ext(encode());
}
h++;
}
scanf("%d",&m);
for (int u=0;u<n;u++)
for (int i=0;i<n;i++)
e[u][i]=MAX;
while (m--)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
x--;y--;
if (x==y) continue;
if (x>y) swap(x,y);
up(e[x][y],z);
}
clr();
for (int u=0;u<(1<<n);u++)
add(1,u,dp[o][u]);
o^=1;
for (int x=0;x<n;x++)
for (int y=0;y<n;y++)
if (e[x][y]<MAX)
{
int z=e[x][y];
clr();
for (int i=1;i<=t;i++)
for (int j=0;j<cnt[o][i];j++)
{
int S=s[o][i][j],f=w[o][i][S];
add(i,S,f);//不用这条边
add(g[i][x][y],S^(1<<x)^(1<<y),f+z);//加入一次
add(g[i][x][y],S,f+z+z);//加入两次
}
o^=1;
}
int ans=MAX;
for (int u=1;u<=t;u++)
if (check(q[u])==true)
{
for (int i=0;i<cnt[o][u];i++)
if (s[o][u][i]==0)
{
up(ans,w[o][u][0]);
break;
}
}
printf("%d\n",ans);
}