https://codeforces.com/contest/1749/problem/C
题意
给定一个长度为 n 的数组 a[]。
游戏开始之前,Alice 选择一个数字 k,表示进行 k 轮游戏。
每一轮 Alice 和 Bob 各对数组 a[] 操作一次:
- 如果当前是第 i 轮的话,Alice 必须删掉一个小于等于
k-i+1
的数; - 然后,如果数组非空的话,Bob 选择一个数使其加上
k-i+1
。
如果到某一轮 Alice 无法操作(剩下元素都比 k-i+1 大 或者 数组为空),Alice 输。
如果 k 轮结束 Alice 都没有输的话,Alice 就赢。
Bob 尽量不让 Alice 赢。如果两者都采取最佳操作,问 Alice 能赢的最大 k 为多少?
1 ≤ t ≤ 100 , 1 ≤ n ≤ 100 , 1 ≤ a i ≤ n 1≤t≤100,\ 1 \le n \le 100,\ 1 \le a_i \le n 1≤t≤100, 1≤n≤100, 1≤ai≤n
思路
也就是说,第 1 轮 Alice 删掉 ≤ k 的一个数,Bob 让一个数加上 k;第二轮 Alice 删掉 ≤ k-1 的一个数,Bob 让一个数加上 k-1;…
一开始想的是,假设当前 Alice 要删掉 ≤ k 的数,她肯定会删掉剩下的数中小于等于 k 的最大的数,把较小的数剩下留着下面的轮次再删(因为下面轮次要删的数会更小)。
然后就想,Bob 会阻止 Alice 执行 k 轮,那么 Bob 就要操作她下一轮能够删掉的数(小于等于 k-1 的最大的数),让她下一轮操作不了,那她就输。
其实 Bob 的这种想法并不是最优的。因为最后 Alice 如果要赢的话一定要操作满 k 轮,而最后一轮 Alice 一定要删掉一个小于等于 1 的数,如果 Bob 在前几轮能够直接阻击掉 Alice 所有最终要删掉的 1,那么 Alice 最后无论如何都不能赢。所以 Bob 的最佳操作应该是,在每一轮操作时,都操作搞掉一个 1。
那么,如果 k 大于数列中 1 的个数 cnt 的话,所有的 1 都会被 Bob 搞掉,Alice 最后一轮无法操作,必输。
所以就只需要枚举 1~cnt,看 Alice 是否每一轮都能操作。
其实发现,如果 k 比较大的时候 Alice 能赢的话,k 比较小的时候一定也能赢,具有二段性,可以二分。
时间复杂度可以优化到O(Tnlognlogn)。(甚至有O(Tnlogn)和O(Tn) 的做法)
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
bool check(int k)
{
multiset<int> st;
for(int i=1;i<=n;i++) st.insert(a[i]);
for(int i=k;i>=1;i--)
{
auto it = st.upper_bound(i);
if(it == st.begin()) return 0;
it --;
st.erase(it);
if(st.size())
{
it = st.begin();
int x = *it;
st.erase(it);
st.insert(x + i);
}
}
return 1;
}
signed main(){
Ios;
cin >> T;
while(T--)
{
cin >> n;
int cnt = 0;
for(int i=1;i<=n;i++){
cin >> a[i];
if(a[i] == 1) cnt ++;
}
int l = 0, r = cnt;
while(l < r)
{
int mid = l + r + 1 >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
// int maxa = 0;
// for(int k=1;k<=cnt;k++) if(check(k)) maxa = max(maxa, k);
// cout << maxa << endl;
}
return 0;
}