[Luogu3959] [NOIP2017] 寶藏 Treasure [狀態壓縮+子集+dp/搜尋+剪枝/模擬退火]
[ ]
注意:洛谷題解裡面有很大一部分題解是錯誤的
具體可以用討論中的hack資料自行驗證。
點數很特殊。
最開始的想法是類似最小生成樹那樣的東西,不過這個“最小”定義實在有點模糊。
注意到資料範圍。n的範圍十分特殊,考慮一下。
按理來說不是搜尋就是狀態壓縮了,而且狀壓的可能性更大一點。
不過還是先考慮搜尋吧。
搜尋
首先列舉起點,然後逐個跑最小生成樹?
要注意的是,邊權不是固定的,和之前的方案有關。所以最小生成樹是跑不了了。
嘗試列舉全排列,同時每一步還要列舉新的點是哪個點拓展出來的。
這樣就能騙不少分了(
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int n,m,dis[15][15]={},dep[15]={};
int pt[15][15]={};
int mn[15]={};
long long ans=0x3f3f3f3f;
bool vis[15]={};
int stk[15]={};
inline void dfs(register int step,register long long sum)
{
if(sum>=ans) return;
if(step==n) { ans=min(ans,sum); return;}
for(register int i,ti=1;ti<=stk[0];++ti)
{
i= stk[ti];
for(register int v,j=1;j<=pt[i][0];++j)
{
v=pt[i][j];
if(vis[v])continue;
vis[v]=1, dep[v]=dep[i]+1; stk[++stk[0]]=v;
dfs(step+1,sum+1ll*dis[i][v]*dep[v]);
vis[v]=0; --stk[0];
}
}
}
int main()
{
memset(mn,0x3f,sizeof(mn));
memset(dis,0x3f,sizeof(dis));
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;++i)dis[i][i]=0;
for(register int u,v,w,i=1;i<=m;++i)
{
scanf("%d%d%d",&u,&v,&w);
if(dis[u][v]==0x3f3f3f3f)
{
pt[u][++pt[u][0]]=v;
pt[v][++pt[v][0]]=u;
}
if(w<dis[u][v])
{
dis[u][v]=dis[v][u]=w;
mn[u]=min(mn[u],w);
mn[v]=min(mn[v],w);
}
}
stk[0]=1;
for(register int i=1;i<=n;++i) { vis[i]=1; dep[i]=0; stk[1]=i; dfs(1,0); vis[i]=0;}
printf("%d",ans);
return 0;
}
沒有特意剪枝,剪枝AC的程式碼可以參考洛谷題解。
模擬退火
在Prim貪心的基礎上隨機接受非最短邊。
具體見洛谷題解
狀態壓縮
狀壓的根本在於按(被壓縮的)層轉移。
初步考慮
表示
裡面的點都被選入,此時的最小代價。
轉移的影響因素有兩個,一個是轉移邊的權值,一個是轉移點的層數
第二個是動態變化的。於是狀態變為
。
所以列舉
和
,然後列舉
表示要放在
層的點集。
複雜度是
。實際上裡面的剪枝剪掉了很多重複的狀態,仔細考慮一下?
實際上因為
是
補集的子集也就是全集的子集的子集,
列舉
的一層複雜度可以無視掉,複雜度實際上就是列舉全集的子集的子集的複雜度。
考慮一個元素數量為 的集合,它的元素個數為 的子集數量為 ,
且元素個數為 的集合的所有子集數量為 。
綜上所述,元素個數為 的集合的子集的子集數量為 。
這個形式應該挺熟悉的,就是二項式定理那個 取 。
所以數量就是
綜上所述複雜度為 。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<cstring>
using namespace std;
int n,m,Ans=0x3f3f3f3f;
int F[15][5000]={};
int dis[15][15]={};
int cost[15][5000]={};
int to[5000][5000]={};
void out(int x)
{
while(x)
{
if(x&1)putchar('1');
else putchar('0');
x>>=1;
}cout<<" ";
}
int main()
{
memset(dis,0x3f,sizeof(dis));
memset(cost,0x3f,sizeof(cost));
scanf("%d%d",&n,&m);
for(register int u,v,w,i=1;i<=m;++i)
{
scanf("%d%d%d",&u,&v,&w);
if(w<dis[u][v])dis[u][v]=dis[v][u]=w;
}
for(int i=1;i<=n;++i)
{
for(int j=0;j<(1<<n);++j)
{
if(j&(1<<i-1))continue;
for(int k=1;k<=n;++k)
{
if(j&(1<<k-1))cost[i][j]=min(cost[i][j],dis[i][k]);
}
}
}
for(int i=0;i<(1<<n);++i)
{
for(int j=0;j<(1<<n);++j)
{
if(i&j)continue;
for(int k=1;k<=n;++k)
{
if(i&(1<<k-1))to[i][j]=min(to[i][j]+cost[k][j],0x3f3f3f3f);
}
}
}
memset(F,0x3f,sizeof(F));
for(int i=1;i<=n;++i)F[1][1<<i-1]=0;
for(register int f=1;f<n;++f)
{
for(register int j=0;j<(1<<n);++j)
{
if(F[f][j]==0x3f3f3f3f)continue;
for(register int k=0