【NOIP2017】宝藏(状压DP/爆搜)

匿名 (未验证) 提交于 2019-12-02 23:55:01

问题:

参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋, 也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。

小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远, 也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路 则相对容易很多。

小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某 个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。

在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以 任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路 所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏 屋之间的道路无需再开发。

新开发一条道路的代价是:

L×K

L代表这条道路的长度,K代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的 宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋) 。

请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代 价最小,并输出这个最小值。

解:

细数我做这道题的心历路程

首先我看到n这么小 我就先定义 了 $f[i]$ 表示集合 都遍历的最小花费

               $dis[j]$ 表示j的深度 (我开始没看出来要选一棵树,还有深度....)

            $dp[i|(1<<j-1)]=min(dp[i|1<<j-1],dis[edge[k].st]*edge[k].val+dp[i])$ 其中终点为不在集合内的j 起点为在集合的st

                原因在于 时间复杂度较高 并且dis数组算的有问题

code:

#include<stdio.h> #include<cstdio>  #include<algorithm>  #include<cmath>  #include<bits/stdc++.h> using namespace std; #define maxn 5555 #define maxnn 5000 #define ll long long  ll dp[maxn]; int dis[maxn]; int tot=0; int all; struct node {     int st,en;     int val; }edge[maxnn]; int n,m; void add(int a,int b,int c) {     edge[++tot].en=b;     edge[tot].st=a;     edge[tot].val=c; } void dfs(int v) {     for(int i=0;i<=n;i++)     dis[i]=0;     for(int i=0;i<=all;i++)     {         dp[i]=100000000000;     }     dis[v]=1;     dp[1<<v-1]=0;     for(int i=1<<v-1;i<=all;i++)     {         for(int j=1;j<=n;j++)         {                 if(((1<<j-1)&i)==0)             {                 for(int k=1;k<=tot;k++)                 {                     if((((1<<edge[k].st-1)&i)!=0)&&(edge[k].en==j)&&(dp[i|1<<j-1]>dis[edge[k].st]*edge[k].val+dp[i]))                     {                         dp[i|(1<<j-1)]=min(dp[i|1<<j-1],dis[edge[k].st]*edge[k].val+dp[i]);                         dis[j]=dis[edge[k].st]+1;                     }                 }             }                      }     } } int main() {     cin>>n>>m;     int x,y,z;     ll ans=100000000000;     all=(1<<n)-1;     for(int i=1;i<=m;i++)     {         scanf("%d%d%d",&x,&y,&z);         add(x,y,z);         add(y,x,z);     }     for(int i=1;i<=n;i++)     {         dfs(i);         ans=min(ans,dp[all]);     }     cout<<ans; }

怎么办?

所以我们有 的$f[i][j]$ 表示 集合i 并且当前讨论到j 层深度的最小花费

状态转移方程也就出来了

$dp[i|k][j]=min(dp[i|k][j],dp[i][j-1]+(j)*\sum k^i)$  其中$\sum k^i $ 为深度为j的集合k到集合i的最小距离之和 k是i的补集的子集  也就是包含了没有被讨论过的点这里我们就要安利一波枚举子集的方法
for(int i=s,i;i=(i-1)&s)
还有一波快速取出集合元素的方法(x就是原来集合的为1的元素)
for(int i=s;i;i=i-(i&-i)) {   int x=i&-i;           }
 

据说这样和logg2数组搭配更快哦

但是

在经历了一个多小时的卡常优化后 我还是成功被卡常

佛了

code:

#include<stdio.h> #include<cstdio>  #include<algorithm>  #include<cmath>  #include<bits/stdc++.h> using namespace std; #define maxn 5555 #define maxnn 5000 #define ll long long  ll dp[maxn][maxnn]; int sum[maxnn][maxnn]; int mapp[30][30]; int dis[maxnn][maxnn]; int tot=0; int all; int n,m; int logg[1<<14];  inline void init() {     for(int i=0;i<=n;i++)     {         for(int j=0;j<=all;j++)         {             dis[i][j]=10000000;         }     }     for(int j=0;j<=all;j++)     {         int s0=j^all;         for(int i=s0;i;i=(i)-(i&-i))         {             int x=i&-i;             for(int k=j;k;k=k-(k&-k))             {                 int u=k&-k;                  {                     dis[logg[x]][j]=min(dis[logg[x]][j],mapp[logg[x]][logg[u]]);                 }             }         }         }     for(int j=0;j<=all;j++)     {         int s=j^all;         for(int u=s;u;u=((u-1)&s))         for(int i=u;i;i=(i)-(i&-i))         {             int x=i&-i;             {                 sum[u][j]+=dis[logg[x]][j];             }         }     } } int main() {     cin>>n>>m;     all=(1<<n)-1;     for(int j=1;j<=n;j++)     {         logg[1<<j-1]=j;     }     for(int j=0;j<=n;j++)     {         for(int i=0;i<=n;i++)         {             if(i!=j) mapp[i][j]=100000000;         }     }     int a,b,c;     for(int i=1;i<=m;i++)     {         scanf("%d%d%d",&a,&b,&c);         mapp[a][b]=min(mapp[a][b],c);         mapp[b][a]=min(mapp[a][b],c);     }     init();     ll ans=1000000000;     for(int i=1;i<=n;i++)     {     for(int j=0;j<=n;j++)     {     for(int i=0;i<=all;i++)         {             dp[i][j]=1000000000;         }     }     dp[1<<i-1][0]=0;     for(int i=0;i<=all;i++)     {         for(int j=1;j<=n;j++)         {             int s0=(all^i);             for(int k=s0;k;k=((k-1)&s0))             {                 dp[i|k][j]=min(dp[i|k][j],dp[i][j-1]+(j)*sum[k][i]);             }         }     }     for(int j=0;j<=n;j++)     {          ans=min(ans,dp[all][j]);     }     }     printf("%lld",ans); }

我同学帮我玄学优化卡过了


爆搜

花费时间:0ms

code:

// #include<bits/stdc++.h> using namespace std; #define maxnn 30 int tar[maxnn][maxnn]; int cnt=0; int n,m; int d[maxnn]; int vis[maxnn]; #define inf 1000000 int tmp=0,tot; int ans=100000000; int c[maxnn][maxnn]; int le[maxnn]; int p; bool cmp(int a,int b){      return c[p][a]<c[p][b]; } void dfs(int num,int node) {     for(int i=num;i<=cnt;i++)     {         if(tot+tmp*le[vis[i]]>ans) return ;         for(int j=node;j<=d[vis[i]];j++)         {             if(!le[tar[vis[i]][j]])             {                 cnt++;                 vis[cnt]=tar[vis[i]][j];                 tmp-=c[vis[cnt]][tar[vis[cnt]][1]];                 tot+=c[vis[i]][vis[cnt]]*le[vis[i]];                 le[vis[cnt]]=le[vis[i]]+1;                 dfs(i,j+1);                 tot-=c[vis[i]][vis[cnt]]*le[vis[i]];                 le[vis[cnt]]=0;                 tmp+=c[vis[cnt]][tar[vis[cnt]][1]];                 cnt--;             }         }         node =1;     }     if(cnt==n)     {         if(tot<ans) ans=tot;         return ;     } } int main() {     int x,y,z;     scanf("%d%d",&n,&m);     for(int i=1;i<=n;i++)     for(int j=1;j<=n;j++)     {         c[i][j]=inf;     }     for(int i=1;i<=m;i++)     {         scanf("%d%d%d",&x,&y,&z);         if(c[x][y]==inf)         tar[x][++d[x]]=y,tar[y][++d[y]]=x;         if(c[x][y]>z)         {             c[x][y]=z;             c[y][x]=z;         }      }     for(int i=1;i<=n;i++)     {         p=i;         sort(tar[i]+1,tar[i]+1+d[i],cmp);         tmp+=c[i][tar[i][1]];     }     for(int i=1;i<=n;i++)     {         tot=0;         cnt=1;         vis[1]=i;         le[i]=1;         tmp-=c[i][tar[i][1]];         dfs(1,1);         le[i]=0;         tmp+=c[i][tar[i][1]];     }     printf("%d",ans); }

 

summay:

*爆搜并不是不可过 爆搜非常重要 要多练爆搜

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!