USCoj 1396- Select Team 状压DP

来源:互联网 发布:淘宝多琳香水是正品么 编辑:程序博客网 时间:2024/06/05 07:20

要做这道题我推荐先做一下我记录的上一道题
http://blog.csdn.net/q610376681/article/details/51330654
看懂了那道题再做这个就简单一点了

题目链接:
http://61.187.179.71:9988/problem.php?id=1396

题目大意:给你n个人,每一个人都有一个能力值,要选出k支队伍,每支队伍三个人,一个队伍中任意两个人配合会能力值加成,能力值加成以一个n*n的矩阵给出,问选出的k队伍能力值加起来最大是多少

解题思路:

考虑状压dp

假设一共有10人 要选出2支队伍
1代表选某个人 0代表没有

某一个状态为 s=1 0 1 0 1 0 1 0 1 1
1 2 3 4 5 6 7 8 9 10

最优解恰好在这个状态里 为 1 3 10与5 7 9组队,我们只需枚举某一个状态里存在的任意三个人组队的能力值,以及剩下的所有人的能力值加成就可以了
比如这个例子 要枚举 i=0 0 0 0 0 0 1 0 1 1 和剩下的s-i
i=0 0 0 0 1 0 0 0 1 1 和剩下的s-i
i=0 0 1 0 0 0 0 0 1 1 和剩下的s-i
…… …
i=1 0 1 0 1 0 0 0 0 0 和剩下的s-i
总之转移就是挑出一个状态s里的任意三个人组成一个队状态为i,剩下的人组成一个队,找出它们的最大值即可
dp[s]=max(dp[s],team[i]+dp[s-i]);
team就是三个人组成一队的能力值,dp[s-i]就是剩下的人组成的能力值。
因为时限卡的很紧,有两个方面要注意:
1.当状态s里1的个数为3的整数倍的时候再进行转移
2.枚举状态s里的任意三个人组成一队的时候,不需要枚举所有的三个人,只需要枚举最后一个1,和其后所有2个1的排列组合即可,因为,前面为三个1的情况我们已经计算过了

#include <iostream>#include <cstring>#include <cstdio>#include <cstdlib>#include <cmath>#include <string>#include <vector>#include <list>#include <map>#include <queue>#include <stack>#include <algorithm>#include <numeric>#include <functional>#define RI(N) scanf("%d",&(N))#define RII(N,M) scanf("%d %d",&(N),&(M))#define RIII(N,M,K) scanf("%d %d %d",&(N),&(M),&(K))#define mem(a) memset((a),0,sizeof(a))using namespace std;const int inf=1e9;const int inf1=-1*1e9;double EPS=1e-10;int k,n;typedef long long LL;int b[25][25];int a[25];int dp[1<<18];int tt[1<<18];int call[1<<18];int tem[20];int cal(int s){    int s1=s;    if(call[s]>=0) return call[s];    int x=0;    while(s)    {        x++;        s&=(s-1);    }    return call[s1]=x;}int main(){    int t;    RI(t);    memset(call,-1,sizeof(call));    while(t--)    {        RII(k,n);        for(int i=0; i<n; i++)            RI(a[i]);        for(int i=0; i<n; i++)            for(int j=0; j<n; j++)                RI(b[i][j]);        memset(dp,0,sizeof(dp));        for(int i=0; i<n; i++)            for(int j=i+1; j<n; j++)                for(int k=j+1; k<n; k++)                {                    int y=1<<i|1<<j|1<<k;                    tt[y]=a[i]+a[j]+a[k]+b[i][j]+b[j][k]+b[i][k];                }        int anss=0;        k*=3;        for(int i=1; i<(1<<n); i++)        {            if(cal(i)%3==0)            {                int poi=0;                for(int j=0; j<n; j++)                {                    if(i>>j&1) tem[poi++]=j;                }                int x=1<<tem[poi-1];                for(int j=0; j<poi-1; j++)                    for(int k=j+1; k<poi-1; k++)                    {                        int y=(1<<tem[j])|(1<<tem[k])|x;                        dp[i]=max(dp[i],tt[y]+dp[i-y]);                    }                if(call[i]==k) anss=max(anss,dp[i]);            }        }        printf("%d\n",anss);    }    return 0;}
1 0