洛谷题目页面传送门 & AtCoder题目页面传送门
给定一个无向连通带权图(G=(V,E),|V|=n,|E|=m)(节点从(0)开始编号),要删掉一些边使得节点(0)到(n-1)有且只有(1)条简单路径,求最小的删掉的边的权值和。
(nin[2,15],minleft[n-1,dfrac{n(n-1)}2 ight]),(G)中没有重边或自环。
这个问题显然可以转化为:求最大的删过边之后的图的边权和,再用原图的边权和减去它。
考率删过边之后的图(G'(V,E'))的样子。(0 o n-1)只有(1)条简单路径,设它为(paleft(pa_1=0,pa_s=n-1,forall iin[1,s),(pa_i,pa_{i+1},len)in E' ight))。若(exists iin[1,s],exists jin[i+1,s]),(pa_i o pa_j)有不止(1)条简单路径,那么显然可以先走(0 o pa_i),然后分别走(pa_i o pa_j)的多条路径,最后走(pa_j o n-1),构造出多条(0 o n-1)路径,不符合题意。可以得到(forall iin[1,s],forall jin[i+1,s]),(pa_i o pa_j)只有(1)条简单路径。所以(G')应该是这样的:(pa)中每个点下面挂着几坨连通的点,各个点挂的点集没有交集(如果有交集,那么这(2)个(pa)中的点之间的简单路径就会不止(1)条)。
最优情况下,(G')一定是连通的。理由:若(G')不连通,由于(G')合法,(0,n-1)一定在一个连通分量内。那么可以在除了(0,n-1)所在连通分量的其他连通分量之间恢复若干被删除的边使得它们连通(由于原图(G)连通,一定可行)。此时(G')还剩(2)个连通分量,设它们分别为(A,B),其中(0,n-1in A)。由于原图(G)连通,一定可以找到一组点((a,b))使得(ain A,bin B,(a,b,len)in E),将((a,b,len))恢复,这样(B)就成为了挂在(pa)中点下新的一坨点或某坨旧点的一份子。如此,可以在(G')上恢复若干被删除的边,使得它更优且合法。所以最优情况下,(G')一定是连通的。
接下来,利用任意(2)坨点都没有交集这个性质,就可以状压DP了。设(dp_{i,j}(0in i))表示(pa)中(0 o j)这条链包括它们下面挂的那些坨点所构成的点集为bitmask(i)时,(left{(x,y,len)mid x,yin i,(x,y,len)in E ight})中可以留下的边的最大权值和。显然,目标为(sumlimits_{(x,y,len)in E}len-dp_{V,n-1}),边界为(dp_{i,0}=sumlimits_{x,yin i,(x,y,len)in E}len)。转移时,枚举(j)下挂的点集,这个点集和({j})的并集中显然应该所有的边都留下,再枚举(pa)中(j)的前一个点,这个点到(j)的边也应该留下。于是状态转移方程就出来了:
这里面有个子集枚举,所以直接按照这个方程转移是(mathrm O!left(3^nn^3 ight))的,过不去。显然,可以预处理出(sum_i=sumlimits_{x,yin i,(x,y,len)in E}len),复杂度降到了(mathrm O!left(3^nn^2 ight)),但还是过不去。接下来要处理(maxlimits_{oin i-k-{j},(o,j,len)in E}left{len+dp_{i-k-{j},o} ight}),不难发现这个式子仅关于(i-k-{j})和(j),而不同的二元组((i-k-{j},j))只有(mathrm O(2^nn))个,这个式子却被计算了(mathrm O(3^nn))次。于是我们可以避免重复计算,记录(tmp_{i,j}=maxlimits_{kin i,(k,j,len)in E}left{len+dp_{i,k} ight}),在每次完成一个(dp_{i,j})的计算时,更新所有与它相关的(tmp)的值,即令(forall k otin i((j,k,len)in E),tmp_{i,k}=max(tmp_{i,k},len+dp_{i,j}))。这样,在状态转移方程中遇到这个式子时,直接调用(tmp)即可。
放一下最终的状态转移方程:
时间复杂度(mathrm O!left(3^nn+2^nn^2 ight)=mathrm O(3^nn))。
最后上代码:
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=15;
int n,m;
int tb[N][N];//邻接矩阵
int sum[1<<N];
int tmp[1<<N][N],dp[1<<N][N];
void prt_bitmsk(int x){
for(int i=0;i<n;i++)cout<<!!(x&1<<i);
}
int main(){
cin>>n>>m;
int tot=0;
while(m--){
int x,y,z;
cin>>x>>y>>z;
x--;y--;
tb[x][y]=tb[y][x]=z;
tot+=z;
}
for(int i=0;i<1<<n;i++){//预处理sum
for(int j=0;j<n;j++)if(i&1<<j)for(int k=j+1;k<n;k++)if(i&1<<k)
sum[i]+=tb[j][k];
// printf("sum[");prt_bitmsk(i);printf("]=%d
",sum[i]);
}
for(int i=0;i<1<<n;i++)for(int j=0;j<n;j++)dp[i][j]=tmp[i][j]=-inf;//初始化
for(int i=0;i<1<<n;i++)if(i&1)for(int j=0;j<n;j++)if(i&1<<j){//DP
if(j)//非边界
for(int k=i^1<<j^1;;k=k-1&(i^1<<j^1)){//枚举j下面挂的点集k
dp[i][j]=max(dp[i][j],sum[k|1<<j]+tmp[i^k^1<<j][j]);//状态转移方程
if(!k)break;
}
else//边界
dp[i][j]=sum[i];
// printf("dp[");prt_bitmsk(i);printf("][%d]=%d
",j,dp[i][j]);
for(int k=0;k<n;k++)if(!(i&1<<k))//更新与dp[i][j]有关的所有tmp[i][k]
if(tb[j][k])
tmp[i][k]=max(tmp[i][k],dp[i][j]+tb[j][k]);
}
cout<<tot-dp[(1<<n)-1][n-1];//目标
return 0;
}