传球游戏之最小总代价

题目

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基本知识

那么状态转移方程很显然就是:

$dp[i][k]=min(dp[i][k],dp[j][k^(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
",ans);//输出answer 
}
原文地址:https://www.cnblogs.com/wzx-RS-STHN/p/13471170.html