CF - C. Number Game(思维,博弈)

这篇博客讨论了一个涉及动态规划和博弈论的问题。Alice和Bob进行一场游戏,Alice需要在给定数组中按规则删除数字,Bob则进行相反操作。目标是找出Alice能赢得最多多少轮游戏。博主分析了Alice和Bob的最佳策略,并通过二分搜索优化了求解过程,最后得出结论,Alice能否获胜关键在于数组中数字1的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 1t100, 1n100, 1ain

思路
也就是说,第 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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值