• 【做题】CSA49F


    原文链接 https://www.cnblogs.com/cly-none/p/CSA49F.html

    题意:Alice和Bob在玩游戏。有(n)种卡牌,每种卡牌有(b_i)张,保证(sum b_i)为偶数。现在,Alice要把所有卡牌任意平分为2份(仅要求每份卡牌数为(frac {sum b_i} {2})),并对每份分别进行一次游戏。第一次游戏由Alice先手,第二次由Bob先手。

    每次游戏中,Alice和Bob会轮流取走一张卡牌直到取尽。设最后Alice有(n_i)张第(i)种牌,那么她会得到(leftlfloor frac {n_i} {a_i} ight floor c_i)的分数。一次游戏的得分是Alice从每种牌得到的分数总和。

    现在,Alice想要最大化两次游戏的得分总和,Bob则想最小化。求出在两人都采取最优决策时的得分总和。

    (n leq 2 imes 10^3, sum a_i leq 2 imes 10^3, sum b_i leq 5 imes 10^5, c_i geq 0)

    先考虑如何计算一次游戏的得分。

    首先,因为是轮流取,故对于每(2a_i)张卡牌(i),都能产生(c_i)的分数。因此,我们可以先考虑这一部分的贡献,然后将所有(n_i)(2a_i)取模。

    接下来,考虑如果(n_i < 2a_i - 1),那么只要Bob跟着Alice取,Alice就得不到这个(c_i)。当(n_i = 2a_i - 1)时,先手就能恰好取到(a_i)张牌,但先后手顺序会交换。

    因此,对于剩下来的卡牌,我们忽略(n_i < 2a_i - 1)的,并对剩下的按(c_i)从大到小排序。那么,若Alice先手,就能得到(sum_{2 mid i} c_i)的分数;否则就是(sum_{2 | i} c_i)

    那么,我们就能得到一个dp的做法。以(c_i)为关键字排序后,设(dp[i,j,a,b])表示当前处理了前(i)种卡牌,第一份已经有(j)张卡牌,且第一份有(a)(n_i)(2a_i)取模后是(2a_i-1)的卡牌,第二份有(b)种。注意到只要记录(a)(b)的奇偶性就可以了。暴力转移,则这个dp的复杂度是(O((sum b_i)^2))

    但这样还不足以解决本题。考虑(sum a_i)比较小,故我们要从这个角度来优化dp。

    先注意到两点,一是我们在转移时,产生的贡献只和放到第一份的数量对(2a_i)取模的值有关;二是dp状态中最庞大的(j),最后只是用来确定第一份的数量等于(frac {sum b_i} {2})的。于是我们考虑对(j)进行优化,目的是在转移时,只用枚举放在第一份的数量对(2a_i)取模的值。

    于是我们把第一份卡牌分为两个部分,一部分是所有(n_i)(2a_i)取模后的结果,则另一部分的卡牌总数就是(sum_{i} 2a_ik_i)的形式。要保证第一份的卡牌总数为一个固定值,我们就要求出(sum_{i} 2a_i k_i)的能表示出哪些数。

    先考虑(k_i)的取值范围。设(n_i mod 2a_i = r_i),那么(k_i)就是在(left[ 0, leftlfloor frac {b_i - r_i} {2a_i} ight floor ight])之间的整数。但(leftlfloor frac {b_i - r_i} {2a_i} ight floor)有两种取值:在(r_i leq b_i mod 2a_i)时,为(leftlfloor frac {b_i} {2a_i} ight floor);否则是(leftlfloor frac {b_i} {2a_i} ight floor - 1)。于是我们不妨就令(k_i)的上界为(leftlfloor frac {b_i} {2a_i} ight floor - 1),当(r_i leq b_i)的时候,把多出来的那个(2a_i)算在第一部分里就可以了。

    剩下就是一个背包问题。注意到我们可以把(a_i)相等的数放在一起计算,而(a_i)只有$ sqrt {sum a_i}(种取值,因此这个问题的复杂度是)O(sqrt {sum a_i} (sum b_i))$的。

    总结一下,第一部分的处理和(O((sum b_i)^2))的算法差不多,但这一部分的复杂度是(O((sum a_i)^2))的。第二部分的背包,复杂度为(O(sqrt {sum a_i} (sum b_i)))

    于是时间复杂度为(O((sum a_i)^2 + sqrt {sum a_i} (sum b_i)))

    #include <bits/stdc++.h>
    using namespace std;
    const int A = 2010, B = 500010, N = 2010;
    int dp[2][A << 2][2][2], bag[B], n, num[A], p, sa, sb, ans;
    struct data {
      int a,b,c;
      bool operator < (const data& x) const {
        return c > x.c;
      }
    } dat[N];
    inline void ckmx(int& x,int y) {
      x = x < y ? y : x;
    }
    int main() {
      scanf("%d",&n);
      for (int i = 1 ; i <= n ; ++ i)
        scanf("%d%d%d",&dat[i].a, &dat[i].b, &dat[i].c);
      sort(dat+1,dat+n+1);
      for (int i = 1 ; i <= n ; ++ i) {
        if (dat[i].b / (dat[i].a << 1) - 1 > 0)
          num[dat[i].a] += dat[i].b / (dat[i].a << 1) - 1;
        sa += dat[i].a;
        sb += dat[i].b;
      }
      p = 1;
      memset(dp,-1,sizeof dp);
      dp[0][0][0][0] = 0;
      for (int i = 1, sum = 0 ; i <= n ; ++ i, p ^= 1) {
        memset(dp[p],-1,sizeof dp[p]);
        for (int j = 0 ; j <= (sum << 2) ; ++ j)
          for (int a = 0 ; a < 2 ; ++ a)
            for (int b = 0 ; b < 2 ; ++ b) {
              if (dp[p^1][j][a][b] == -1) continue;
              for (int k = 0 ; k < 2 * dat[i].a && k <= dat[i].b ; ++ k) {
                int tmp = (dat[i].b - k) / (dat[i].a << 1), na = a, nb = b;
                if (k == 2 * dat[i].a - 1) {
                  if (!na) tmp ++;
                  na ^= 1;
                }
                if ((dat[i].b - k) % (dat[i].a << 1) == (dat[i].a << 1) - 1) {
                  if (nb) tmp ++;
                  nb ^= 1;
                }
                tmp = tmp * dat[i].c;
                ckmx(dp[p][j + k][na][nb], dp[p^1][j][a][b] + tmp);
                if (dat[i].b >= 2 * dat[i].a && k <= dat[i].b % (dat[i].a << 1))
                  ckmx(dp[p][j + k + 2 * dat[i].a][na][nb], dp[p^1][j][a][b] + tmp);
              }
            }
        sum += dat[i].a;
      }
      bag[0] = 1;
      for (int i = 1 ; i < A ; ++ i) {
        if (!num[i]) continue;
        for (int j = 0 ; j <= sb ; ++ j) {
          if (bag[j]) bag[j] = 0;
          else {
            bag[j] = -1;
            if (j >= 2 * i) {
              if (bag[j - 2 * i] != -1)
                bag[j] = bag[j - 2 * i] + 1;
            }
          }
        }
        for (int j = 0 ; j <= sb ; ++ j)
          if (bag[j] == -1) bag[j] = 0;
          else if (bag[j] <= num[i]) bag[j] = 1;
          else bag[j] = 0;
      }
      p ^= 1;
      for (int i = 0 ; i <= (sa << 2) && i <= (sb >> 1) ; ++ i)
        for (int a = 0 ; a < 2 ; ++ a)
          for (int b = 0 ; b < 2 ; ++ b) {
            if (dp[p][i][a][b] == -1) continue;
            if (bag[(sb >> 1) - i]) ans = max(ans, dp[p][i][a][b]);
          }
      printf("%d
    ",ans);
      return 0;
    }
    

    小结:这个问题相当有难度。得到(O((sum b_i)^2))的dp已经偏难,而后一部分的优化对思维能力和细节处理能力的要求,是在博主目前能力之上的,也体现了算法优化的一些重要思路。

  • 相关阅读:
    Go微服务全链路跟踪详解
    让Windows加入域的PowerShell
    关掉Windows Firewall的PowerShell
    修改IP地址的PowerShell
    如何得知当前机器上安装的PowerShell是什么版本的?
    如何在Vblock里配置Boot from SAN
    使用MDS Switch基本命令的一个例子
    [转贴]SSL工作原理
    [转贴] 数字证书及 CA 的扫盲介绍
    什么是Copy-Only Backup? 为什么要用它?
  • 原文地址:https://www.cnblogs.com/cly-none/p/CSA49F.html
Copyright © 2020-2023  润新知