传球游戏之最小总代价
题目
Description
n个人在做传递物品的游戏,编号为1-n。游戏规则是这样的:开始时物品可以在任意一人手上,他可把物品传递给其他人中的任意一位;下一个人可以传递给未接过物品的任意一人。即物品只能经过同一个人一次,而且每次传递过程都有一个代价;不同的人传给不同的人的代价值之间没有联系;求当物品经过所有n个人后,整个过程的总代价是多少。
Input
n个人在做传递物品的游戏,编号为1-n。游戏规则是这样的:开始时物品可以在任意一人手上,他可把物品传递给其他人中的任意一位;下一个人可以传递给未接过物品的任意一人。即物品只能经过同一个人一次,而且每次传递过程都有一个代价;不同的人传给不同的人的代价值之间没有联系;求当物品经过所有n个人后,整个过程的总代价是多少。
Output
一个数,为最小的代价总和。
Sample Input
2 -1 9794 2724 –1
Sample Output
2724
思路
这是一道状压$dp$ 的入门题;
首先我们可以设 $dp[i][k]$ 表示球到了第$i$ 个手里并且经过了的人编号的集合为 $k$;
就是 $dp[3][1,5,3]$ 表示传到了第$3$ 个人手里并且经过了$1,5,3$ 这几个人;
那么怎么去表示一个集合呢;
用一个二进制数表示如果第$i$ 位二进制是$1$,代表这个集合包涵 $i$;
具体基础的状压知识请看:
那么状态转移方程很显然就是:
$dp[i][k]=min(dp[i][k],dp[j][k \oplus (1<<(i-1))]+a[j][i]);$
$k$ 是枚举的一个集合,$i$ 和 $j$ 都是枚举的第几个人;
代码
#include<bits/stdc++.h> #define re register typedef long long ll; using namespace std; inline ll read() { ll a=0,f=1; char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();} while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();} return a*f; } ll n; ll a[20][20]; ll dp[20][1<<16]; int main() { memset(dp,127/3,sizeof(dp));//初始化数组 n=read(); for(re ll i=1;i<=n;i++) for(re ll j=1;j<=n;j++) a[i][j]=read();//读入 for(re ll i=1;i<=n;i++) dp[i][1<<(i-1)]=0;//因为可以从任意一个人开始 for(re ll k=1;k<=(1<<n)-1;k++)//枚举每个集合 for(re ll i=1;i<=n;i++) { if(!(k&(1<<(i-1))))//如果i 不在这个集合,那么进入下个循环 continue; for(re ll j=1;j<=n;j++) { if(i==j)//每个人不能从自己手里接过球 continue; if(k&(1<<(j-1)))//必须集合包涵 j ,也就是球传到 j,才能从 j 转移过来 dp[i][k]=min(dp[i][k],dp[j][k^(1<<(i-1))]+a[j][i]);//状态转移方程 }//修改代码艰难痕迹 // cout<<i<<" "<<k<<endl; // cout<<dp[i][k]<<endl; } ll ans=1<<30; for(re ll i=1;i<=n;i++) ans=min(ans,dp[i][(1<<n)-1]);//每个人统计一次,并且可以从任意人手里结束 printf("%lld\n",ans);//输出answer }