Codeforces Round 940 (Div. 2)

这场还挺Edu的

1|0C. How Does the Rook Move?

Problem - C - Codeforces

  • 数学方法

​ 我用的数学方法,卡了很久才推出来思路和式子。

​ 首先行列其实等价,直接单考虑剩余 n 行就行。

​ 这类题应该选择先选一个东西,然后处理剩下的东西。

​ 这里好做的方法是先选 m(r,c)(r=c) 的格子,每次会耗费一个格子,后选 nm (r,c)(rc)的格子,每次会耗费两个格子。

​ 因为后选的格子每次会耗费两个,所以要保证留给后选的数目必须为偶数。

​ 所以大体流程就是枚举所有的 m(m<n,(nm)2),然后推式子:

​ 这里推选 m 个的比较直接:(nm)

​ 但是剩下的那个卡了我半小时:

​ 我们还剩下 nm 行/列,而且必须是偶数(第2类棋不能走完奇数行/列)。

​ 我们可以看到,剩余列的 (nm)! 每一种排列都对应着一组我们可以下的棋步。例如,如果我们还剩下 (1,4,5,6) 列,那么 (4,5,6,1) 排列对应于下 (4,5),(6,1) 步棋。然而,如果我们只是简单地计算排列数,那么我们也将计算排列 (6,1,4,5) ,它对应的是同一组棋步。

为了消除多算,我们可以用 (nm)! 除以 ((nm)/2)! (去除所选棋子对的排列)。

​ 因此,答案变为

c=0m[(nm)mod2=0](nm)(nm)!(nm2)!

void solve() { #define tests int n, k; std::cin >> n >> k; std::vector<bool> vis(n); for (int i = 0, r, c; i < k; i++) { std::cin >> r >> c; --r, --c; vis[r] = vis[c] = true; } int cntNotVis(std::count(all(vis), false)); Z ans {}; for (int m = cntNotVis & 1; m <= cntNotVis; m += 2) { ans += comb.binom(cntNotVis, m) * comb.fac(cntNotVis - m) / comb.fac((cntNotVis - m) / 2); } std::cout << ans << '\n'; }
  • 动态规划

​ 赛时也有往这个方向考虑,但是不知道怎么转移。

​ 移动基本上有两种类型:

  1. 在某个 (i,i) 位置放置车:这将减少 1 的空闲行列数。
  2. 将车置于 (i,j) ,其中 ij :现在计算机也会这样做,将车置于 (j,i) 处,挡住 ij 行以及 ij 列。因此空闲行列数减少了 2

​ 首先,我们算出之前下过的 k 步,并计算剩余可放置车的空闲列/行的数量,称之为 m

​ 注意移行/列的顺序并不影响车的最终配置,因此只有行数才是决定最终配置数的关键。

​ 定义 dp[i] 表示当剩下 i 行和列时的最终配置数。

​ 由于移除行/列的顺序并不重要,我们从移除最后一行或最后一列开始。

​ 在移除 i×i 网格中的最后一行或最后一列时,我们有两种选择:

  • 我们放置车 (i,i) ,结果是只删除最后一行和一列,留下一个 (i1)×(i1) 格。这种情况下的最终配置数为 dp[i1]
  • 或者,我们也可以在 (i,j)(j,i) 中的任意 j{1,2,,i1} 放置车。这步棋之后, j th和 i th行列都被删除,剩下一个 (i2)×(i2) 格。这就为 dp[i] 贡献了 2(i1)dp[i2]

总的来说,我们计算了所有 i{2,3,,n}dp[i]=dp[i1]+2(i1)dp[i2] ,基本情况为 dp[0]=dp[1]=1

我们的最终答案是 dp[m]

while (t--) { int n, k; cin >> n >> k; int used = 0; for (int i = 0; i < k; i++) { int r, c; cin >> r >> c; used += 2 - (r == c); } int m = n - used; dp[0] = dp[1] = 1; for (int i = 2; i <= m; i++) dp[i] = (dp[i - 1] + 2ll * (i - 1) * dp[i - 2] % MOD) % MOD; cout << dp[m] << "\n"; }

2|0D. A BIT of an Inequality

Problem - D - Codeforces

二进制拆位前缀和,只是维护的是到当前这位的 1 的个数为奇数的个数。

维护出来之后对每个 y 找最高位的 1,显然 f(x,z)f(y,z) 的这一位的 1 的个数之和只要为偶数,那么 f(x,y)f(y,z)>f(x,z) 因为此时 f(x,z) 这一位为 0 了,而 f(x,y)f(y,z) 这一位仍为 1

显然为偶数的情况要么是奇数加奇数,要么是偶数加偶数

void solve() { #define tests int n; std::cin >> n; std::vector<int> a(n); for (auto& x : a) std::cin >> x; std::vector dp(31, std::vector<int>(n + 1)); // 拆位前缀和 for (int i = 0; i < 31; i++) { int sum {}; for (int j = 0; j < n; j++) { sum = (sum + (a[j] >> i & 1)) & 1; // 统计1的个数是不是奇数 if (sum == 1) { dp[i][j + 1] = dp[i][j] + 1; } else { dp[i][j + 1] = dp[i][j]; } } } i64 res {}; for (int i = 0; i < n; i++) { int p {}; for (int j = 30; j >= 0; j--) { // 找这个数最大为1的位 if (a[i] >> j & 1) { p = j; break; } } // 奇数 + 奇数的方案 i64 add1(1LL * dp[p][i] * (dp[p][n] - dp[p][i])); i64 add2(1LL * (i + 1 - dp[p][i]) * (n - i - (dp[p][n] - dp[p][i]))); // 偶数 + 偶数的方案 res += add1 + add2; } std::cout << res << '\n'; }

__EOF__

本文作者Kdlyh
本文链接https://www.cnblogs.com/kdlyh/p/18149939.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   加固文明幻景  阅读(347)  评论(4编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示