Hdu5693 D Game

D Game

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 905    Accepted Submission(s): 324

Problem Description
众所周知,度度熊喜欢的字符只有两个:B 和D。
今天,它发明了一个游戏:D游戏。
度度熊的英文并不是很高明,所以这里的D,没什么高深的含义,只是代指等差数列[(等差数列百科)](http://baike.baidu.com/view/62268.htm)中的公差D。
这个游戏是这样的,首先度度熊拥有一个公差集合{D},然后它依次写下N个数字排成一行。游戏规则很简单:
1. 在当前剩下的有序数组中选择X(X2) 个连续数字;
2. 检查1选择的X个数字是否构成等差数列,且公差 d{D}
3. 如果2满足,可以在数组中删除这X个数字;
4. 重复 13 步,直到无法删除更多数字。
度度熊最多能删掉多少个数字,如果它足够聪明的话?
Input
第一行一个整数T,表示T(1T100) 组数据。
每组数据以两个整数 NM 开始 。接着的一行包括 N 个整数,表示排成一行的有序数组 Ai。接下来的一行是 M 个整数,即给定的公差集合 Di
1N,M300
1 000 000 000Ai,Di1 000 000 000
Output
对于每组数据,输出最多能删掉的数字 。
Sample Input
3 3 1 1 2 3 1 3 2 1 2 4 1 2 4 2 1 3 4 3 1 2
Sample Output
3 2 4
Source
分析:挺好的一道题.
   删数,区间会变动,支持O(n^3)的做法,很显然,这就是一道区间dp题.如果直接设状态f[i][j]表示区间[i,j]最多能删多少个数,不好转移,不知道剩下还有哪些数能够删.
   把最优性问题转化成判断可行性问题. 令f[i][j]表示区间[i,j]的数能不能被删光. 有一个结论:每次删数只需要删2个或者3个.为什么可以这样呢?如果一次删的数的个数>3个,那么完全可以把它们拆成2个和3个地去删.可以利用这个结论来进行转移.
   区间[i,j]有两种可能的情况:1.i和j被删了.  2.i和j没有被删.
   对于第一种情况,枚举一个k,如果f[i][k] = f[k + 1][j] = 1,那么f[i][j] = 1.
   对于第二种情况,有两种决策:要么删2个数,要么删3个数,删两个数的话就只能删i和j咯,如果f[i + 1][j - 1] = 1并且a[j] - a[i]在给定的公差集合里,那么f[i][j] = 1. 如果是删3个数,枚举一个中间点k,f[i + 1][k - 1] = f[k + 1][j - 1] = 1并且a[j] - a[k] = a[k] - a[i]并且a[j] - a[k]出现在了公差集合中,那么f[i][j] = 1.
   这只是处理出了区间能否被删除,如何求删除的数的最大数量呢?
   每个数只能被删一次,可以将之前处理的f[i][j]看作一条条线段[i,j],要找不相交的线段使得覆盖的长度最大,这个O(n^2)dp以下就好了.
   最近做的几道区间dp题都是很有套路的,首先是很容易能够看出区间dp的特征,设出基本状态,如果不能转移就加维度.所求的一般都是最值,如果状态不能直接表示成最值,就表示为是否可行.
#include <map>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long ll;
map<ll,int> s;
ll T,n,m,can[310][310],f[310],a[310];

int main()
{
    scanf("%lld",&T);
    while (T--)
    {
        s.clear();
        memset(can,0,sizeof(can));
        memset(f,0,sizeof(f));
        scanf("%lld%lld",&n,&m);
        for (int i = 1; i <= n; i++)
            scanf("%lld",&a[i]);
        for (int i = 1; i <= m; i++)
        {
            ll x;
            scanf("%lld",&x);
            s[x] = 1;
        }
        for (int len = 2; len <= n; len++)
        {
            for (int i = 1; i + len - 1 <= n; i++)
            {
                int j = i + len - 1;
                if (len == 2)
                {
                    if (s[a[j] - a[i]])
                        can[i][j] = 1;
                    continue;
                }
                if (len == 3)
                {
                    if (a[j] - a[j - 1] == a[j - 1] - a[i] && s[a[j] - a[j - 1]])
                        can[i][j] = 1;
                    continue;
                }
                for (int k = i + 1; k < j - 1; k++)
                    if (can[i][k] && can[k + 1][j])
                        can[i][j] = 1;
                if (can[i + 1][j - 1] && s[a[j] - a[i]])
                    can[i][j] = 1;
                for (int k = i + 3; k < j - 2; k++)
                    if (can[i + 1][k - 1] && can[k + 1][j - 1] && a[j] - a[k] == a[k] - a[i] && s[a[j] - a[k]])
                        can[i][j] = 1;
            }
        }
        for (int i = 1; i <= n; i++)
            for (int j = 1; j < i; j++)
                {
                    f[i] = max(f[i],f[i - 1]);
                    if (can[j][i])
                        f[i] = max(f[j - 1] + i - j + 1,f[i]);
                }
        printf("%lld\n",f[n]);
    }

    return 0;
}

 

posted @ 2018-02-28 13:48  zbtrs  阅读(184)  评论(0编辑  收藏  举报