D题dp做法
dp的方法感觉更通用些,可以推广到举手数更多的情况,可惜赛时d了半天也没d出来。。。
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef unsigned long long ULL; constexpr int INF = 0x3f3f3f3f; constexpr int MOD = 1e9 + 7; int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); int n; cin >> n; string s; cin >> s; // 预处理前缀‘1’的数目,方便待会计算输赢次数 vector<int> pre(n + 1); for (int i = 1; i <= n; i++) { pre[i] = pre[i - 1] + (s[i - 1] == '1'); } LL ans = 0; vector<vector<vector<LL>>> memo(n, vector<vector<LL>>(3, vector<LL>(3, -1))); // dfs(i, cnt, add) 表示下标为 i 时,还有 cnt 次举手机会, // 并且已经在 i 之前的 add 个 '0' 处使用了举手机会情况下,符合条件“任意时刻胜场数量不小于负场数量”的 // 举手方案总数 auto dfs = [&](auto self, int i, int cnt, int add) -> LL { if (i >= n) { return 1; } if (memo[i][cnt][add] != -1) { return memo[i][cnt][add]; } LL res = 0; int win = pre[i] + add + (s[i] == '1'); // 由于已经在 i 之前的 add 个 '0' 处使用了举手机会,故胜场数需要加上 add int lose = i + 1 - win; if (win < lose) { // 胜场数小于负场数,则此次一定要用一次举手机会 if (cnt > 0) { res += self(self, i + 1, cnt - 1, add + (s[i] == '0')); memo[i][cnt][add] = res; return memo[i][cnt][add]; } else { memo[i][cnt][add] = 0; return 0; } } res += self(self, i + 1, cnt, add); // 不举手 if (cnt > 0) { res += self(self, i + 1, cnt - 1, add + (s[i] == '0')); // 举手 } memo[i][cnt][add] = res; return res; }; // 由于题目要求的是“恰好”有 2 局是举手的,而 dfs(dfs, 0, 2, 0) 算的是所有方案数 // 故需要减去举手数小于等于1的情况 cout << dfs(dfs, 0, 2, 0) - dfs(dfs, 0, 1, 0) << '\n'; return 0; }