Kick Start 2019 Round H. Elevanagram

设共有 N=_i=19Ai 个数字。先把 N 个数字任意分成两组 ABA 中有 NA=\floorN/2 个数字,B 中有 NB=\ceilN/2 个数字。将 A 中数字之和记作 SAB 中数字之和记作 SB。若 (SASB)mod110,再进行调整。

考虑通过交换 AB 中的数字来改变 SASB 的值。若有解一定能通过若干次交换操作使得 SASBmod11=0。用 (a,b) 表示将 A 中的数字 aB 中的数字 b 进行交换,交换 a,b 之后 SASB 的变化量是 2(ab)

注意到若存在两个数字 i,j 都出现了至少 10 次,则答案一定是 YES。因为当 k 取遍 1102k(ij) 也取遍 110(在模 11 的意义下)。

若每个数字都出现了不到 10 次,可以用 DP 解决。令 f[i][j][k] 表示从 A 中取出 1,2,,ii 种数字共 j 个且取出的数字之和模 11 等于 k 是否有可能。对 B 也进行同样的 DP。
补充:看了官方题解之后发现这个 DP 不必分别对 A 做一次再对 B 做一次。可以只对 A 做一次 DP,方法是把 DP 的第三维改成前 i 个数字造成的差值模 11 等于 k。如此定义 DP 状态,最后答案就是 f[9][\floorN/2][0]。下面的 DP 也可以如此改造。

考虑只有一个数字出现了至少 10 次的情形。设此数字是 d。设 dA 中出现了 dA 次,在 B 中出现了 dB 次。

可以证明任意操作序列 (a1,b1),,(an,bn) 都可以化成等价的操作序列 (a1,b1),,(ak,bk) 且满足对于任意 1i,jkaibj

由于一定存在 d 只作为 ai 或只作为 bi 的操作序列,我们只需考虑长度不超过 t=maxN_Ad_A,N_Bd_B 的操作序列。于是我们可以将 AB19 的每个数字取出至多 t 个,对这两组数目较少的数字进行 DP。

int main() {
 int T;
 scan(T);
 rep (T) {
   kase();
   vi a(10);
   up (i, 1, 9) scan(a[i]);
   int n10 = 0; //出现次数大于等于10的数字的个数
   up (i, 1, 9) {
     n10 += a[i] >= 10;
   }
   if (n10 >= 2) {
     println("YES");
     continue;
   }
   ll n = accumulate(all(a), 0LL);
   ll m = n / 2;
   vi b(10);
   // 先随机分配,再通过DP判断能否进行调整。
   up (i, 1, 9) {
     if (a[i] < m) {
       swap(a[i], b[i]);
       m -= b[i];
     } else {
       b[i] = (int)m;
       a[i] -= (int)m;
       break;
     }
   }

   ll sa = 0, sb = 0;
   up (i, 1, 9) {
     sa += a[i] * i;
     sb += b[i] * i;
   }
   ll r = (sa - sb) % 11;
   if (r == 0) {
     println("YES");
     continue;
   }
   if (r < 0) {
     r += 11;
   }
   // r = r / 2;
   // (11 + 1) / 2 是 2 在模 11 下的拟元。
   r = r * (11 + 1) / 2 % 11;
   // dp[i][j][k] 在前i种数里选择j个数余数是否可能是k
   // 用滚动数组去掉dp数组第一维。
   int na = 0, nb = 0;

   up (i, 1, 9) {
     if (a[i] < 10) {
       na += a[i];
     }
     if (b[i] < 10) {
       nb += b[i];
     }
   }
   auto t = max(na, nb);
   up (i, 1, 9) {
     a[i] = min(a[i], t);
     b[i] = min(b[i], t);
   }
   na = accumulate(all(a), 0);
   nb = accumulate(all(b), 0);
   vv<int> A(na + 1, vi(11));
   vv<int> B(nb + 1, vi(11));
   A[0][0] = 1;
   B[0][0] = 1;

   int ca = 0, cb = 0;

   up (i, 1, 9) {
     if (a[i] > 0) {
       down (j, ca, 0) {
         rng (k, 0, 11) {
           if (A[j][k]) {
             up (l, 1, a[i]) {
               A[j + l][(k + l * i) % 11] = true;
             }
           }
         }
       }
       ca += a[i];
     }
     if (b[i] > 0) {
       down (j, cb, 0) {
         rng (k, 0, 11) {
           if (B[j][k]) {
             up (l, 1, b[i]) {
               B[j + l][(k + l * i) % 11] = true;
             }
           }
         }
       }
       cb += b[i];
     }
   }

   bool flag = false;
   up (i, 1, min(na, nb)) {
     rng (j, 0, 11) {
       if (B[i][j] && A[i][(j + r) % 11]) {
         flag = true;
         break;
       }
     }
     if (flag) break;
   }
   println(flag ? "YES" : "NO");
 }
  return 0;
}
posted @   Pat  阅读(457)  评论(0编辑  收藏  举报
编辑推荐:
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
阅读排行:
· ThreeJs-16智慧城市项目(重磅以及未来发展ai)
· .NET 原生驾驭 AI 新基建实战系列(一):向量数据库的应用与畅想
· Browser-use 详细介绍&使用文档
· 软件产品开发中常见的10个问题及处理方法
· Vite CVE-2025-30208 安全漏洞
历史上的今天:
2018-11-19 hihoCoder [Offer收割]编程练习赛83 D 生成树问题
点击右上角即可分享
微信分享提示