【NOIp2017】宝藏

题面 

https://www.luogu.org/problem/P3959

题解

考场上写的最暴力的状压$dp$,计算量是$2e8$级别的,但是$Van$的评测机非常没有信仰,发现只有$80pts$(只比真暴力多了$10$分)。。。。

注意到层与层之间的转移是独立的。

设$F[i][S0][S1]$为当前是第$i$层,$S0$是$1$到$i-1$层的节点集合,$S1$是第$i$层的节点集合。它的最小代价是多少。

转移的时候枚举一个下一层的节点是啥,然后用这一层的和它连上去转移就行了。

#include<cstdio>
#include<cstring>
#include<iostream>
#define ri register int
#define S 540000
#define B 4096
#define N 12
#define INF 1000000007
using namespace std;

inline int read() {
  int r=0,f=0; char c=getchar();
  while (c<'0' || c>'9') f|=(c=='-'),c=getchar();
  while (c>='0' && c<='9') r=(r<<1)+(r<<3)+(c^48),c=getchar();
  return f?-r:r;
}

int n,m;
int dis[N][N],pp[N],num[B];
int stk[S];
int f[S],g[N][S];

inline int trans(int s,int t) {
  return num[s]+num[t]*2;
}

inline void chkmin(int &a,int b) {
  if (b<a) a=b;
}

int main() {
  freopen("treasure.in","r",stdin);
  freopen("treasure.out","w",stdout);
  pp[0]=1;
  for (ri i=1;i<12;i++) pp[i]=pp[i-1]*3;
  num[0]=0;
  for (ri i=1;i<B;i++) {
    for (ri j=0;j<12;j++) if (i&(1<<j)) {
      num[i]=num[i-(1<<j)]+pp[j];
      break;
    }
  }
  n=read(); m=read();
  memset(dis,0x3f,sizeof(dis));
  for (ri i=1;i<=m;i++) {
    int u=read()-1,v=read()-1,w=read();
    if (dis[u][v]>w) dis[u][v]=dis[v][u]=w;
  }
  memset(f,0x3f,sizeof(f));
  memset(g,0x3f,sizeof(g));
  
  int U=(1<<n)-1;
  for (ri s=0;s<=U;s++) {
    int top=0;
    for (ri s0=s;s0;s0=(s0-1)&s) stk[++top]=s0;
    f[trans(s^U,0)]=0;
    while (top) {
      int s0=stk[top]; top--;
      for (ri i=0;i<n;i++) if (s0&(1<<i)) {
        int a=trans(s^U,s0);
        int b=trans(s^U,s0-(1<<i));
        for (ri j=0;j<n;j++) if ((s^U)&(1<<j) && dis[i][j]<INF) {
          f[a]=min(f[a],f[b]+dis[i][j]);
        }
        break;
      }
    }
  }
  for (ri j=0;j<n;j++) g[0][trans(0,1<<j)]=0;
  for (ri i=0;i<n-1;i++)
    for (ri s=0;s<=U;s++)
      for (ri s0=s;s0;s0=(s0-1)&s) {
        int tot=((s^U)|(s0))^U;
        for (ri s1=tot;s1;s1=(s1-1)&tot) if (f[trans(s0,s1)]<INF) {
          chkmin(g[i+1][trans((s^U)|s0,s1)],g[i][trans(s^U,s0)]+f[trans(s0,s1)]*(i+1));
        }
      }
  int ans=INF;
  for (ri i=1;i<n;i++)
    for (ri s=0;s<=U;s++) if (g[i][trans(s,U^s)]<ans) ans=g[i][trans(s,U^s)];
  cout<<ans<<endl;
}

然后就是一个神奇的优化,其实我们没必要记录当前层是什么,直接记录$S=S0|S1$即可,然后强制把它们连到第$i+1$层即可。

正确性对我来说可以形象的理解一下。

$\mbox{sto aysn orz}$

#include <cstdio>
#define min(a,b) ((a)<(b)?(a):(b))

const int maxn=12,inf=1000000000;
int e[maxn][maxn],f[maxn][1<<maxn],tr[1<<maxn][maxn];

int main(){
    freopen("treasure.in","r",stdin);
    freopen("treasure.out","w",stdout);
    int n,m,u,v,w,ans;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&u,&v,&w);
        --u;--v;
        if(!e[u][v] || e[u][v]>w)e[u][v]=e[v][u]=w;
    }
    for(int i=0;i<(1<<n);++i)
        for(int j=0;j<n;++j)if(i&(1<<j))
            for(int k=0;k<n;++k)if(!(i&(1<<k)) && e[j][k])
                if(!tr[i][k] || tr[i][k]>e[j][k])tr[i][k]=e[j][k];
    for(int i=0;i<n;++i)
        for(int j=0;j<(1<<n);++j)
            f[i][j]=inf;
    for(int i=0;i<n;++i)
        f[0][1<<i]=0;
    for(int i=1;i<n;++i)
        for(int j=0;j<(1<<n);++j)if(f[i-1][j]!=inf){
            int t=((1<<n)-1)^j;
            for(int k=t;k;k=(k-1)&t){
                ans=0;
                for(int a=0;a<n;++a)if(k&(1<<a)){
                    if(!tr[j][a]){ans=-1;break;}
                    else ans+=tr[j][a]*i;
                }
                if(ans>=0)f[i][j|k]=min(f[i][j|k],f[i-1][j]+ans);
            }
        }
    ans=inf;
    for(int i=0;i<n;++i)ans=min(ans,f[i][(1<<n)-1]);
    printf("%d\n",ans);
    return 0;
}
posted @ 2019-11-10 15:26  HellPix  阅读(138)  评论(0编辑  收藏  举报