补题链接:Here

1527A. And Then There Were K

题目大意:

给一个正整数n,求最大的k,使得 Codeforces Round #721 (Div. 2) AB思维,B2博弈,C题map - 图1%20%5C%26%20(n%E2%88%922)%20%5C%26%20(n%E2%88%923)%20%5C%26%20%E2%80%A6%20(k)%20%3D%200#card=math&code=n%20%5C%26%20%28n%E2%88%921%29%20%5C%26%20%28n%E2%88%922%29%20%5C%26%20%28n%E2%88%923%29%20%5C%26%20%E2%80%A6%20%28k%29%20%3D%200)

思路:

就假设 Codeforces Round #721 (Div. 2) AB思维,B2博弈,C题map - 图2 为 17,二进制为 10001,我们来模拟一下求解过程。

  1. 17 10001
  2. 16 10000
  3. 15 01111

因为按位与的特点就是,一位上只要有一个0,这一位最后就是0。

我们竖着来看,17到15,每一位上就都出现过一次0了,所以15就是答案。然后举更多例子观察特点,发现,答案就是让n的二进制最左边的1置为0,后面全置为1。

如:

10001 11101 10111的答案都是 01111

【AC代码】

  1. int qpow(int a, int b) {int ans = 1; while (b) {if (b & 1)ans = ans * a; b >>= 1; a = a * a;} return ans;}
  2. void solve() {
  3. int n;
  4. cin >> n;
  5. int ans = 0;
  6. while (n) {
  7. n >>= 1;
  8. ++ans;
  9. }
  10. cout << qpow(2, ans - 1) - 1 << "\n";
  11. }

转化思维,模拟上面的过程

  1. void solve() {
  2. ll n, k = 0;
  3. cin >> n;
  4. while (k * 2 + 1 < n)k = k * 2 + 1;
  5. cout << k << "\n";
  6. }

遇到这种位运算的题目,一般都是把数的二进制表示出来,然后根据运算的特点(比如&的特点就是,只要有一个0,最后就是0),找规律。

1527B1. Palindrome Game (easy version)

题目大意:

给一个字符串(这题的字符串一开始一定是一个回文)。

1.可以把一个0变为1,操作者的数字+1。

2.或者翻转整个字符串(前提是该字符串不是回文且上一个人的操作不是翻转)。

Alice先,最后字符串全为1时,谁的数字大谁输,相同则平局。

思路:
因为一开始就是回文,所以Alice只好进行1操作,如果可以在这个操作之后仍然让它时一个回文,那Alice就赢定了。

比如:10001

Alice进行1之后,10101

Bob没法翻转,只好进行1操作,11101

这时,Alice很聪明,不会傻傻地让自己的数字变大,选择2翻转字符串,10111

Bob只好继续1操作,111111

这时Bob都已经数字为2了,Alice为1,所以Alice胜

比如:10101

Alice 1操作之后并不是回文,这就让Bob有机可乘,最后是Bob赢了

关键在于,Alice第一次操作之后它还是不是回文,而这取决于改字符串中间的字符是不是0,只有中间的字符是0,且字符串长为奇数(偶数的话连中间都没有)的时候Alice赢,否则Bob赢。

还有一个特殊情况,就是只有一个0的时候,因为刚开始就是回文,Alice只好1操作,所以Alice必定+1,而Bob是0,所以Bob赢了。

  1. void solve() {
  2. int n; string s;
  3. cin >> n >> s;
  4. int cnt0 = count(s.begin(), s.end(), '0');
  5. if (cnt0 == 1)cout << "BOB\n";//只有一个0
  6. else if (n & 1 and s[n / 2] == '0')cout << "ALICE\n";//是奇数个,中间为0
  7. else cout << "BOB\n";
  8. }

压缩判断条件:奇数个0 并且不为 1 个,此时一定是 Alice 获胜了

  1. void solve() {
  2. int n; string s;
  3. cin >> n >> s;
  4. int k = count(s.begin(), s.end(), '0');
  5. if (k & 1 and k != 1)cout << "ALICE\n";
  6. else cout << "BOB\n";
  7. }

1527B2. Palindrome Game (hard version)

题目大意:

这题跟B1的区别就是,一开始的字符串可能不是回文。

思路:

一开始是回文时,用B1的结论。

一开始不是回文时,Alice就可以一开始就翻转,让Bob喘不过气来,所以Bob不可能赢。

但是有一种平局的情况,001,只有2个0,且中间是0。

  1. void solve() {
  2. int n; string s;
  3. cin >> n >> s;
  4. int k = count(s.begin(), s.end(), '0');
  5. string b = s;
  6. reverse(b.begin(), b.end());
  7. if (b == s) { // 用 1 的结论
  8. if (k == 1 || !(n & 1))cout << "BOB\n";
  9. else if ((n & 1) and s[n / 2] == '0')cout << "ALICE\n";
  10. else cout << "BOB\n";
  11. } else {
  12. if (k == 2 and (n & 1) and s[n / 2] == '0')
  13. cout << "DRAW\n";
  14. else cout << "ALICE\n";
  15. }
  16. }

赛后 DP 写法:很复杂,不推荐

  1. const int N = 1010;
  2. int dp[N][N][2][2];
  3. int vis[N][N][2][2];
  4. int DP(int a, int b, int c, int d) {
  5. if (vis[a][b][c][d])return dp[a][b][c][d];
  6. vis[a][b][c][d] = 1;
  7. int &tmp = dp[a][b][c][d] = N;
  8. if (a + b + c == 0) tmp = 0;
  9. if (b and d) tmp = min(tmp, -DP(a, b, c, 0));
  10. if (a) tmp = min(tmp, -DP(a - 1, b + 1, c, 1) + 1);
  11. if (b) tmp = min(tmp, -DP(a, b - 1, c, 1) + 1);
  12. if (c) tmp = min(tmp, -DP(a, b, c - 1, 1) + 1);
  13. return tmp;
  14. }
  15. void solve() {
  16. int n; string s;
  17. cin >> n >> s;
  18. int a = 0, b = 0, c = 0;
  19. for (int i = 0; i * 2 + 1 < n; ++i) {
  20. if (s[i] == '0' and s[n - i - 1] == '0')a++;
  21. if (s[i] != s[n - 1 - i]) b++;
  22. }
  23. if (n & 1 and s[n / 2] == '0')c = 1;
  24. int ans = DP(a, b, c, 1);
  25. if (ans > 0)cout << "BOB\n";
  26. else if (ans == 0)cout << "DRAW\n";
  27. else cout << "ALICE\n";
  28. }

1527C. Sequence Pair Weight

题目大意:

给一个数组,求每个子序列的一对相同数字的数量之和。

分析:

暴力 Codeforces Round #721 (Div. 2) AB思维,B2博弈,C题map - 图3#card=math&code=%5Cmathcal%7BO%7D%28n%5E2%29),肯定不行,所以肯定有什么 Codeforces Round #721 (Div. 2) AB思维,B2博弈,C题map - 图4#card=math&code=%5Cmathcal%7BO%7D%28n%29) 的方法可以实现。

首先,答案肯定跟这些数字出现的次数有关,其次,也跟数字所在的位置有关。

所以很容易想到用 map 去存储位置并计数

  1. void solve() {
  2. map<int, ll>mp;
  3. ll ans = 0;
  4. int n;
  5. cin >> n;
  6. for (int i = 1, a; i <= n; ++i) {
  7. cin >> a;
  8. ans += mp[a] * (n - i + 1);
  9. mp[a] += i;
  10. }
  11. cout << ans << "\n";
  12. }