$O(n*3^n)$好难想...还有好多没见过的操作
令$f[i][j]$表示最深深度为i,点的状态为j的最小代价,每次枚举状态$S$后,计算$S$的补集里的每个点与S里的点的最小连边代价,再$O(3^n)$枚举S补集的子集,$g[x]$表示补集里状态为x的点往S集合里的点连边的最小代价,然后转移的时候加上它就好。
但是$g[x]$怎么处理呢...处理不好就会变成$O(3^n*n^2)$了,当然也可以预处理,但是有更简单的方法。因为我们枚举补集的时候是按顺序的,当前状态去掉最低位的状态一定是算过了的,于是就可以用减去lowbit的$g[x-lowbit(x)]$加上最低位往S的某个点连边的最小代价来得到。
学习到的一些技巧是枚举状态之后每次减去lowbit得到所有的点效率可以提高一些,用于卡常,还有就是上方的$O(n^3)$就能预处理出$g[x]$的方法,都好喵喵啊~
#include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; const int maxn=13, inf=6e6; int n, m, x, y, z; int mp[maxn][maxn], f[maxn][1<<12], g[1<<12], h[1<<12], Log[1<<12], a[maxn], mncost[maxn]; inline void read(int &k) { int f=1; k=0; char c=getchar(); while(c<'0' || c>'9') c=='-' && (f=-1), c=getchar(); while(c<='9' && c>='0') k=k*10+c-'0', c=getchar(); k*=f; } inline int min(int a, int b){return a<b?a:b;} int main() { read(n); read(m); memset(mp, 32, sizeof(mp)); for(int i=1;i<=m;i++) read(x), read(y), read(z), mp[x][y]=mp[y][x]=min(mp[x][y], z); for(int i=0;i<n;i++) Log[1<<i]=i+1; memset(f, 32, sizeof(f)); for(int i=1;i<=n;i++) f[1][1<<(i-1)]=0; int st=(1<<n)-1, ans=inf; for(int i=1;i<=n;i++) { for(int j=0;j<=st;j++) { int cnt=0; for(int k=st-j;k;k-=k&-k) { int x=Log[k&-k]; a[++cnt]=x; mncost[x]=inf; for(int l=j;l;l-=l&-l) mncost[x]=min(mncost[x], min(1ll*inf, 1ll*mp[Log[l&-l]][x]*(i-1))); } for(int k=0;k<(1<<cnt);k++) { g[k]=g[k-(k&-k)]+mncost[a[Log[k&-k]]]; h[k]=k?h[k-(k&-k)]|(1<<(a[Log[k&-k]]-1)):0; f[i][j|h[k]]=min(f[i][j|h[k]], f[i-1][j]+g[k]); } } ans=min(ans, f[i][st]); } printf("%d ", ans); return 0; }