A.Problem - A - Codeforces
关键点分析
-
选择条件:在选择的过程中,Bob 的选择受限于 Alice 选择的数字。具体来说,Bob 必须选择一个与 Alice 选择的数字 aaa 满足条件 a+b≡3(mod4)a + b \equiv 3 \pmod{4}a+b≡3(mod4)。这意味着给定 Alice 选择的 aaa,Bob 选择的 bbb 只能满足这个条件。
-
模4的分布:观察可以得出:
-
每个数字 aaa 在 000 到 n−1n-1n−1 范围内,都有一个模 4 的余数值(即 a%4a \% 4a%4)。对于每个 aaa,Bob 需要根据 a+b≡3(mod4)a + b \equiv 3 \pmod{4}a+b≡3(mod4) 来选择 bbb。
-
这个条件将模4的余数与另一个余数关联起来。具体来说:
-
如果 a%4=0a \% 4 = 0a%4=0,则 b%4=3b \% 4 = 3b%4=3
-
如果 a%4=1a \% 4 = 1a%4=1,则 b%4=2b \% 4 = 2b%4=2
-
如果 a%4=2a \% 4 = 2a%4=2,则 b%4=1b \% 4 = 1b%4=1
-
如果 a%4=3a \% 4 = 3a%4=3,则 b%4=0b \% 4 = 0b%4=0
-
-
-
胜负判断:考虑到 Alice 先手,游戏的结果与初始模4的分布有关。我们可以通过模拟这种选择过程来判断谁会先被卡住。
解题思路
-
模4的分布:可以通过计算每个数字 aaa 对 4 取余的分布情况,确定每个余数类别中有多少数字。通过模拟 Alice 和 Bob 的选择,我们可以确定谁会输掉游戏。
-
博弈的动态规划:每当 Alice 或 Bob 选择一个数字并擦去后,游戏的状态就会发生变化。我们可以使用动态规划来模拟这个过程,直到没有有效选择。
C
Problem - C - Codeforces
因为常规思想写出来会超时所以选择用预处理带动态规划的思想,通过预处理 避免重复计算,并利用 辅助数组 提供快速访问来优化时间复杂度。常见的优化手段还有 动态规划 、分治法 、滑动窗口 等。通过这类预处理的技巧,很多复杂度较高的问题可以被简化为线性时间复杂度,从而大大提高程序的效率。
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N];
int minpre[N],maxsu[N];
void solve()
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
minpre[0]=a[0];
for(int i=1;i<n;i++)
minpre[i]=min(minpre[i-1],a[i]);
maxsu[n-1]=a[n-1];
for(int i=n-2;i>=0;i--)
maxsu[i]=max(maxsu[i+1],a[i]);
cout<<1;
for(int i=1;i<n-1;i++)
{
bool f1=a[i]<minpre[i-1];
bool f2=a[i]>maxsu[i+1];
if(f1 || f2) cout<<1;
else cout<<0;
}
cout<<1<<endl;
}
int main()
{
int t = 1;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
D:二进制字符串博弈判断器
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
void solve()
{
int n,k;
string s;
cin>>n>>k>>s;
int cnt=0;
for(auto a:s)
if(a=='1')
cnt++;
cout<<(cnt<=k || n<2*k ? "Alice" :"Bob")<<endl;
}
int main()
{
int t = 1;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
✅ 第一种情况:cnt <= k
这是最简单直接的:
-
Alice 可以选择任意子序列(不要求连续),长度为
k
。 -
如果字符串中总共只有 ≤
k
个1
,Alice 第一轮就可以全选中这些1
,直接全部清零,立即获胜 ✅。
✅ 第二种情况:n < 2 * k
这个判断稍微复杂一点,需要从 博弈角度分析。
思路如下:
-
Alice 每轮可以"任意选"
k
个字符变成0
(子序列,任意位置)。 -
Bob 每轮只能"连续选"
k
个字符变成1
(子串,必须连续)。 -
现在,如果总长度
n < 2*k
,Bob 的能力会变得 局限,他能影响的空间变小。
举例:
-
Bob能操作的连续子串,一共只有
(n - k + 1)
种可能。 -
Alice可以任意选择非连续字符的位置来置
0
,她的覆盖能力远远强于 Bob。
所以当 n < 2*k
时,Alice 有足够的操作空间可以克制 Bob 的行为 ,最终能把全部字符变成 0
,从而赢得胜利。
E.频率统计 + mex + 分类 + set维护
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
#define ll long long int;
void solve()
{
int n;
cin>>n;
vector<int> a(n);
unordered_map<int,int> cnt;
unordered_map<int,vector<int>> ss;
int mx=0;
for(int i=0;i<n;i++)
{
cin>>a[i];
cnt[a[i]]++;
while(cnt.find(mx)!=cnt.end())
mx++;
}
for(pair<int,int> p:cnt)
{
ss[p.second].push_back(p.first);
}
set<int> res;
res.insert(mx);
for(int k=0;k<=n;k++)
{
res.erase(n-k+1);
for(int i:ss[k])
{
if(i<=min(n-k,mx))
res.insert(i);
}
cout<<res.size()<<' ';
}
cout<<endl;
}
int main()
{
int t = 1;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
核心思路:
1.MEX是由出现频率控制的
2.某个值是否能成为MEX,取决于它之前的值是否都出现过,而他本身没有出现
3.对于每个频率f,记录有哪些值是出现了f次的,用ss[f]
4.模拟删掉k个元素时,某些数会"从数组中消失"或"频率降低"->会影响MEX值
变量含义解释:
unordered_map<int,int> cnt; // 记录每个数出现的次数
unordered_map<int,vector<int>> ss; // 每个频率对应出现了该频率的所有数
int mx = 0; // mx 表示初始时数组的 MEX
分类出现频率(核心)
for(pair<int,int> p:cnt)
{
ss[p.second].push_back(p.first);
}
这一步将所有数按频率分类,例如:
cnt = {
0: 2次,
1: 2次,
2: 1次
}
会得到:
ss[2] = [0,1]
ss[1] = [2]