P3959 宝藏
又是毒瘤的noip2017年的原题。
考场上太菜了,prim就得了20分。呜呜呜
\(n\)这么小,不状态都对不起这道题呀
原本,我是想的是,dfs搜索,状压记录状态。
发现,这个东西,一个状态会被重复刷新。不满足无后效性。
然后,考虑按照层数拓展,一层拓展上若干个点。
恩,无后效性解决了。可是这个复杂度看起来是\((2^n\cdot 2^n)\)的呀?
不会爆么?
问题的本质是什么?
问题的本质是问题的本质......
是从一个集合中选出一个子集来,然后再选出与第一个子集没有交集的子集。
假如说我们第一次要选出\(x\)个元素的子集。显然有\(C_n^x\)种选法。
剩下的如何去选呢? 更显然,有\(2^{n-x}\)种选法。
总的方法就是\(C_n^x\cdot 2^{n-x}\)
把所有的关于x的式子加起来,然后利用二项式定理。发现变成了\(3^n\)
然后再按照状态进行转移。总时间复杂度为\(3^nn^3\)看似是七千多万,然鹅都较多状态是没有用的。
然后时间复杂度就成立\(O(\text{能过})\)
存图的话,邻接矩阵就行了。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
using std::min;
const int N=12;
int M[N][N];
int f[(1<<N)+1][N+1];
int dis[N];
int main()
{
memset(f,-1,sizeof(f));
memset(M,-1,sizeof(M));
int n,m;
scanf("%d%d",&n,&m);
int a,b,c,all=(1<<n)-1;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
a--;b--;
if(M[a][b]==-1) M[a][b]=0x7fffffff;
M[a][b]=M[b][a]=min(M[a][b],c);
}
for(int i=0;i<n;i++) f[1<<i][1]=0;
for(int i=1;i<(1<<n);i++)
{
int opp=all^i;
for(int j=opp;j;j=(j-1)&opp)
{
memset(dis,-1,sizeof(dis));
bool F=false;
for(int k1=0;k1<n;k1++) if((1<<k1)&i)
for(int k2=0;k2<n;k2++) if((1<<k2)&j)
{
if(M[k1][k2]==-1) continue;
if(dis[k2]!=-1)
dis[k2]=min(dis[k2],M[k1][k2]);
else
dis[k2]=M[k1][k2];
}
int tot=0,pas;
for(int k=0;k<n;k++)
if((1<<k)&j)
{
if(dis[k]==-1) {F=true;break;}
tot+=dis[k];
}
if(F) continue;
pas=tot;
for(int k=1;k<=n;k++)
{
if(f[i][k]!=-1)
{
if(f[i|j][k+1]==-1)
f[i|j][k+1]=0x7fffffff;
f[i|j][k+1]=min(f[i|j][k+1],f[i][k]+pas);
}
pas+=tot;
}
}
}
int ans=0x7fffffff;
for(int i=0;i<=n;i++)
if(f[all][i]!=-1)
ans=min(ans,f[all][i]);
printf("%d",ans);
}