CF(codeforces) 101E Candies and Stones题解(空间卡爆~~~)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Q1410136042/article/details/83624537

Candies and Stones传送门

题目背景:

Little Gerald and his coach Mike play an interesting game. At the beginning of the game there is a pile consisting of n candies and a pile consisting of m stones. Gerald and Mike move in turns, Mike goes first. During his move Mike checks how many candies and stones Gerald has eaten. Let Gerald eat a candies and b stones. Then Mike awards Gerald f(a,b) prize points. Gerald during his move either eats a candy from the pile of candies or a stone from the pile of stones. As Mike sees that Gerald has eaten everything apart one candy and one stone, he awards points for the last time and the game ends. Gerald is not allowed to eat all the candies, and he is not allowed to eat all the stones too. Tell Gerald how to play to get the largest possible number of points: it is required to find one of the possible optimal playing strategies for Gerald.

题目大意:

给定一个n*m的矩阵,初始位于左上角,每次可以向右,或者向下走一步,在每一点都可以获得一个分数,问走到右下角的时候最大分数是多少,还有输出路径。

矩阵给出方式:给出x[n]数组,y[m]数组,模数p,则矩阵(i,j)位置分数f[i][j] = (x[i] + y[j]) % p【这意味着初始分数为(x[0]+y[0])%p】

题目规模:1 ≤ n, m ≤ 20000, 1 ≤ p ≤ 1e9,0 ≤ x[i], y[i]  ≤ 20000

路径输出方式:向右一步输出一个‘S',向下一步输出一个'C',最终路径是一个长长的字符串~~

时间:7.5s

空间:46080 kB也就是45MB

题解:

这一题时间还行,O(n*m)的DP可以过,然鹅,这个空间,45M是真滴恐怖。。

首先如果不考虑路径输出的话,45M空间何解?这个不难,直接滚动数组即可。显然dp[i][j] = max(dp[i-1][j], dp[i][j-1])+w[i][j],算到第i行的时候,影响结果的只有第i行和第i-1行~~那么为了节省空间,第i-2行的空间就可以释放掉,或者说用来存第i行的结果,然后第i-1行的空间用来存第i+1行的结果,这样只需要要两个m长度的数组反复滚动即可,显然不会爆45M。

现在考虑路径输出,按照以前的方法,直接开一个int pre[m][n],pre[i][j]用来记前置点,这个最大空间是                                                                       sizeof(int)*20000*20000 kB =1600MB

这个空间显然是不行的。然后注意到这一题路径每一步只有两个值’S'和'C',也就是说这是个非0即1的问题,那么我们就可以给上面的空间压缩32倍,用使用BitMap算法用1 bit的空间来存储一步即可~~(没听说过的可以看看BitMap算法)这样空间变为1600M/32=50M,离45M还是差了点。。。这出题人可真是煞费苦心啊。。。

要知道CF的题是有标签的,这一题标签是分治和DP,DP用到了,那么怎么分治呢?

下面来考虑分治策略:

如上图,从中间将矩阵分成两部分,显然要从左上角走到右下角,必定会经过中线上至少一个点。这意味着如果我们可以找到中间的一个中转点,先存储左上角到中转点的路径,输出之,然后再用刚刚的空间存储中转点到右下角的路径,再输出。这样一来,我们的空间再次减半,空间成功压缩成25M,耐斯~~~~

这个怎么实现呢?

第一步,计算左上角到中线每一点能够得到的最大分数,同时记录这一半的前置点(从左边转移过来则记为0,从上面转移过来记为1),时间复杂度O(0.5*n*m)。

第二步,反向计算右下角到中线每一点能够得到的最大分数,这次不记录前置点了(记录的话会把刚刚记的覆盖掉),时间复杂度为O(0.5*n*m)

第三步,遍历中线,找到 (从左上角到它的最大分数 + 从右下角到它的最大分数) 最大的点K,显然,必定有一条经过该点的路径能够使得分最大,时间复杂度O(n)

第四步,输出左上角到K的路径,平均复杂度时间复杂度O(n),输出后记录半部分前置点的数组就可以重新利用用来存储后半部分的前置点了~~

第五步,再次重新计算从K到右下角的最大分数,并记录前置点(这次主要是为了记录前置点),平均时间复杂度O(n/2 * m/2) = O(0.25*n*m)

第六步,输出后半部分路径,平均复杂度时间复杂度O(n)

综合以上,此题涉及算法DP、滚动数组、BitMap(或者说状压)、分治,时间复杂度为O(1.25n*m),以int为单位的话,空间复杂度为O(n*m/64)(当然我是直接开到最大的20010*20010/64)

AC代码:

#include<bits/stdc++.h>
#define ll long long

using namespace std;
const int maxn = 20010;
struct Bitmap
{
    unsigned bitmp[maxn>>5];
    void updateToOne(const int& x)  {   bitmp[x>>5] |= (unsigned)1<<(x&31);         }
    void updateToZero(const int& x) {   bitmp[x>>5] &= ~((unsigned)1<<(x&31));      }
    bool getBit(const int& x)       {   return bitmp[x>>5] & ((unsigned)1<<(x&31));  }
}from[maxn>>1];//存前置点,为0表示从左(S),为1表示从上(C)

int x[maxn], y[maxn];
ll sUp[maxn], sDown[maxn];//从左上点到中间一行的最大得分和从右下点到中间一行的最大得分
ll sFirst[maxn], sSecond[maxn];//滚动数组啊~~~
char path[40020];
int main()
{
#ifdef AFei
freopen("in.c", "r", stdin);
#endif // AFei

    int n, m, p;
    scanf("%d%d%d", &n, &m, &p);
    for(int i = 0; i < n; ++ i)
        scanf("%d", &x[i]);
    for(int i = 0; i < m; ++ i)
        scanf("%d", &y[i]);

    ll scoreMax = -1;
    int midX = n/2, midY = -1;

    // 处理左上端点到中间线的距离及路径
    sFirst[0] = (x[0] + y[0]) % p;
    for(int j = 1; j < m; ++ j) //处理第0行
        sFirst[j] = sFirst[j-1] + (x[0] + y[j]) % p,
        from[0].updateToZero(j);

    for(int i = 1; i <= midX; ++ i)
    {
        for(int j = 0; j < m; ++ j)
        {
            int sc = (x[i] + y[j]) % p;
            if(j && sSecond[j-1] >= sFirst[j])
            {
                sFirst[j] = sSecond[j] = sSecond[j-1] + sc;
                from[i].updateToZero(j);
            }
            else
            {
                sFirst[j] = sSecond[j] = sFirst[j] + sc;
                from[i].updateToOne(j);
            }
        }
    }
    for(int i = 0; i < m; ++ i)
        sUp[i] = sFirst[i];

    // 处理右下角到中间线的距离,从下到上,从右到左
    sFirst[m-1] = (x[n-1] + y[m-1])%p;
    for(int j = m-2; ~j; -- j)  // 处理最后一行
        sFirst[j] = sFirst[j+1] + (x[n-1] + y[j])%p;
    for(int i = n-2; i >= midX; -- i)
    {
        for(int j = m-1; ~j; -- j)
        {
            int sc = (x[i] + y[j]) % p;
            if(j < m-1 && sSecond[j+1] >= sFirst[j])
                sFirst[j] = sSecond[j] = sSecond[j+1] + sc;
            else
                sFirst[j] = sSecond[j] = sFirst[j] + sc;
        }
    }
    for(int i = 0; i < m; ++ i)
        sDown[i] = sFirst[i] - (x[midX] + y[i])%p;

    // 处理出最大值及中转点
    for(int i = 0; i < m; ++ i)
        if(sDown[i] + sUp[i] > scoreMax)
            scoreMax = sDown[i] + sUp[midY=i];

    cout << scoreMax << endl;

    int nowX = midX, nowY = midY, cnt = midX+midY;
    while(nowX || nowY)
    {
        bool flag = from[nowX].getBit(nowY);
        if(flag)
            -- nowX, path[-- cnt] = 'C';
        else
            -- nowY, path[-- cnt] = 'S';
    }

    // 处理从(midX,midY)到(n-1, m-1)的路径
    sFirst[midY] = (x[midX] + y[midY]) % p;
    for(int j = midY; j < m; ++ j) //处理第midX行
        sFirst[j] = sFirst[j-1] + (x[midX] + y[j]) % p,
        from[0].updateToZero(j);

    for(int i = midX+1; i < n; ++ i)
    {
        for(int j = midY; j < m; ++ j)
        {
            int sc = (x[i] + y[j]) % p;
            if(j != midY && sSecond[j-1] >= sFirst[j])
            {
                sFirst[j] = sSecond[j] = sSecond[j-1] + sc;
                from[i-midX].updateToZero(j);
            }
            else
            {
                sFirst[j] = sSecond[j] = sFirst[j] + sc;
                from[i-midX].updateToOne(j);
            }
        }
    }

    nowX = n-1, nowY = m-1, cnt = m+n-2;
    while(nowX > midX || nowY > midY)
    {
        bool flag = from[nowX-midX].getBit(nowY);
        if(flag)
            -- nowX, path[-- cnt] = 'C';
        else
            -- nowY, path[-- cnt] = 'S';
    }

    // 输出路径
    cnt = m+n-2;
    path[cnt ++] = '\0';
    printf("%s\n", path);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Q1410136042/article/details/83624537