hdu 2291 Five in a Row, Again

20 篇文章 0 订阅

 hdu 2291 Five in a Row, Again

好几天了,这个题做完了一只没有总结,今天总结一下

原题传送门:http://acm.hdu.edu.cn/showproblem.php?pid=2291

这个题我用的是状态压缩的dp,方法很笨,但是能ac,呵呵

先看看我查这个题解题报告时觉得不错的一个资料,其中给出了五种解法:

原文出自http://hi.baidu.com/spellbreaker/blog/item/5fd2f02ece315b301f308995.html

第四届华中北地区程序设计邀请赛 Five in a Row, Again 解题报告
2009-04-17 15:12

这一题命题人的本意是状态DP,而且还有一个“非常巧妙”的优化思想。但事实上非常遗憾,很少有人按照命题人的本意做了这道题。

事实上回想起来,这一题确实有很多可以改进的地方,官方给出的结题报告也比较糟糕。

各个AI可以增长的经验值均为正值,因此最优策略必定是趁其他AI还未变强之前让1号与所有人打一轮,这一点是毋庸置疑的,以下我总结一些可以AC的办法,按复杂度从劣到优,依次有:

1.搜索最优的排列,复杂度N!,这里N只有13,还可以剪枝,能够A掉。

2. 用p二进制表示与哪些人比过赛,F[p][x]表示比过赛的人状态为p,获得经验值为x,能取得的最大分数,进行状态DP。命题人给我们讲解此题时,认为这是一般队员能想到的方法,但是会超内存。的确在TJU上不能用这个方法,但现场赛时不限内存,包括我们队在内的一些队都是这么水过去的,这是本题的又一失败之处。

3.F[p][x]表示“赛况”为p,1号AI获得x积分时所能得到的最大经验值,进行状态DP。这是标程使用的办法,命题人说“这个思想很巧妙”,但我觉得命题人一直把“赛况”和“最大经验值”分开的错误是十分明显的,作这样的优化让人哭笑不得。

4.用F[p]表示比过赛的人状态为p,能取得的最大分数,进行状态DP。可以看到如果确定与哪些人比过赛,经验值就是确定的,因为于每个人比赛后都增加固定的经验值,无论输赢。而N很小,可以打一张表,记录所有已经打过比赛的人的组合所对应的经验值,不打表临时算也可以。复杂度是N*2^N

5.搜索输哪些人,或者赢哪些人。事实上这并不是一个求排列的问题,而是一个求组合的问题,通过一个可行的组合一定可以生成这样一个可行的排列:对于要输的人,先跟它打,因为反正是输,提前增加经验值不会有坏处。而输完以后对于要赢的人,从经验值由低到高依次打,因为如果能打得过强的就一定打得过弱的,打完弱的后经验值只会增加,仍然打得过强的。这个方法复杂度只有2^N,而且容易加入很多剪枝,我相信即使N增加到30,也不会有问题。

 

我的思路是用12位二进制表示AI打过或者没打过,然后就是每一次找一个没打过的染ai打,递归的调用下去,直到所有的人ai都打过,我觉得这个题很像旅行商问题,每个点访问且仅访问一次。

 

最近做了不少关于状态压缩的题,这个是其中最简单的一个

下面是我的源码:

#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
int dp[1024 * 8][140];
int w[20][20];
int e[20][20];
int s[20];
int n;

int dfs(int fs,int x)
{
    if(dp[fs][x] != -1) return dp[fs][x];
    dp[fs][x] = 0;
    for(int i = 0;i < n-1;i++)
        if( (fs & (1 << i)) == 0)
        {
            int ss = (fs | (1 << i));
            if(x > s[i+1])
                dp[fs][x] = max(dp[fs][x],dfs(ss,x+w[0][i+1]) + e[0][i+1]);
            else dp[fs][x] = max(dp[fs][x],dfs(ss,x+w[0][i+1]));
        }
    return dp[fs][x];
}
int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        cin >> n;
        for(int i = 0;i < n;i++)
            for(int j = 0;j < n;j++)
                cin >> w[i][j];
        for(int i = 0;i < n;i++)
            for(int j = 0;j < n;j++)
                cin >> e[i][j];
        for(int i = 0;i < n;i++)
            cin >> s[i];
        memset(dp,-1,sizeof(dp));
        dfs(0,s[0]);
        int ma = 0;



        /*for(int i = 0;i < 150;i++)
        {
            cout << (1<<(n-1))-1 << " " << dp[(1<<(n-1))-1][i] << "\n";
            ma = max(ma,dp[0][i]);
        }*/

        cout << dp[0][s[0]] << "\n";
    }

    return 0;
}


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值