[每日随题4] 博弈论 - 贪心 - DP

整体概述

  • 难度:1000 -> 1500 -> 2000

1695B.Circle Game

  • 标签:博弈论

  • 前置知识:无

  • 难度:Div.3.C 1000

题目描述:

image

输入格式:

image

输出格式:

image

样例输入:

2
1
37
2
100 100

样例输出:

Mike
Joe

解题思路:

  • 由于一个人操作结束,另一方一定操作下一堆石子,二者第一圈一定互不干扰。

  • 所以如果 \(n\) 为奇数,第一堆石子第二圈时一定是 \(Joe\) 去取,所以 \(Mike\) 只需要在第一次把第一堆石子取光一定获胜。

  • \(n\) 为偶数,我们发现两个人每一圈取的都是那几堆,\(Mike\) 取奇数,\(Joe\) 取偶数,互不干扰。因此双方每次只取一颗,看谁先取完谁失败。

    所以我们需要找到各自数量最少的,最靠左的那堆石子,谁更小谁先取完失败。若数量相同,则更靠左的那个会先取,先落败。

  • 找到最小最靠左的那堆石子后,分类讨论即可。

完整代码

#include<bits/stdc++.h>
#define Size(x) ((int)(x).size())
#define Min(x,y) min((int)(x),(int)(y))
#define int long long
using namespace std;
const int N = 5e5+5,INF = 1e9+1;
inline string solve(){
	int n; cin >> n;
	int odd_mn = INF,even_mn = INF,odd_pos = -1,even_pos = -1;
	for(int i=1,x;i<=n;i++){
		cin >> x;
		if(i%2 == 1 && x < odd_mn) odd_mn = Min(odd_mn,x),odd_pos = i;
		else if(x < even_mn) even_mn = Min(even_mn,x),even_pos = i;
	}
	if(n&1) return "Mike";
	if(odd_mn == even_mn) return odd_pos < even_pos ? "Joe" : "Mike";
	return odd_mn < even_mn ? "Joe" : "Mike";
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T; cin >> T;
	while(T--) cout << solve() << '\n';
	return 0;
}

1742G.Orray

  • 标签:贪心

  • 前置知识:无

  • 难度:Div.4.G 1500

题目描述:

image

输入格式:

  • 输入的第一行包含一个整数 \(t\) \((1\le t\le100)\) ,表示测试用例的数量。测试用例说明如下。

    每个测试用例的第一行包含一个整数 \(n\) \((1\le n\le2⋅10^5)\),表示数组 \(a\) 的长度。

    每个测试用例的第二行包含 \(n\) 个非负整数 \(a_1,…,a_n\) \((0\le a_i\le 10^9)\)

  • 保证所有测试用例中 \(n\) 的总和不超过 \(2⋅10^5\)

输出格式:

  • 为每个测试用例打印 \(n\) 个整数:数组 \(a\) 的任意重新排列,以获得词序最大的前缀 \(OR\) 数组。

样例输入:

5
4
1 2 4 8
7
5 1 2 3 4 5 5
2
1 101
6
2 3 4 2 3 4
8
1 4 2 3 4 5 7 1

样例输出:

8 4 2 1 
5 2 1 3 4 5 5 
101 1 
4 3 2 2 3 4 
7 1 4 2 3 4 5 1 

解题思路:

  • 首先发现,第一位一定放 \(\max_{i=1}^n a_i\) 才能让第一位最大,接着保证字典序最大。

  • 最朴素的想法是,对于之后每一位的答案,暴力枚举哪一个剩余数字 \(OR\) 上后最大,这是 \(O(n^2)\) 的显然会超时。

  • 但我们仔细一想,按位 \(OR\) 的操作每次要更大,必然是将某一位甚至多位从 \(0\) 或成了 \(1\),而一个 \(int\) 范围的数字只有 \(32\) 位,那么我们最多只会进行 \(32\) 次选择数字,之后无论怎么或都不会更大了。

  • 所以我们只需要暴力枚举到某个时刻,当前操作得到的答案,和上一步操作得到的 \(OR\) 值相同,说明后续的 \(OR\) 值再也不会改变了,后续的排列顺序无需考虑直接随意输出即可。

  • 故最劣复杂度为 \(O(32*n)\)

完整代码

#include<bits/stdc++.h>
#define Size(x) ((int)(x).size())
#define int long long
using namespace std;
const int N = 2e5+5;
int a[N]; bool vis[N];
inline void solve(){
	int n; cin >> n;
	for(int i=1;i<=n;i++) vis[i] = false;
	for(int i=1;i<=n;i++) cin >> a[i];
	sort(a+1,a+1+n,[&](int x,int y){return x>y;});
	vis[1] = true; cout << a[1] << ' '; 
	int last = a[1];	// last 用来记录上一个 OR 值
	while(true){
		int pos = -1, mx = last;
		for(int i=1;i<=n;i++){
			if(vis[i]) continue;
			if((last|a[i]) > mx) pos = i, mx = last|a[i];
		} 
		if(pos == -1) break;
		last = mx, vis[pos] = true;
		cout << a[pos] << ' ';
	}
	for(int i=1;i<=n;i++) if(!vis[i]) cout << a[i] << ' ';
	cout << '\n';
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T; cin >> T;
	while(T--) solve();
	return 0;
}

629C.Famil Door and Brackets

  • 标签:线性DP

  • 前置知识:无

  • 难度:Div.2.C 2000

题目描述:

image

输入格式:

image

image

输出格式:

image

样例输入:

4 1
(
4 4
(())
4 3
(((

样例输出:

4
1
0

解题思路:

  • 题目所求为一个非常大的方案数,考虑 \(DP\)

    此外发现题目给出了一个特殊的数据范围 \(n-m\le 2000\),则 \(DP\) 的构造与该输入范围有关。

  • 我们发现需要构造的 \(P,Q\) 两段都可以被看为一类相同格式的字符串:长度为 \(i\),左括号比右括号多 \(j\) 个。所以定义 \(DP\) 数组,\(dp_{i,j}\) 表示构造长度为 \(i\),左括号比右括号多 \(j\) 个的字符串的总方案数。

    那么 \(DP\) 的转移很容易得到:\(dp_{i,j} = dp_{i-1,j-1} + dp_{i-1,j+1}\)

  • 因此我们可以在 \((n-m)^2\) 的时间内得到整个 \(DP\) 数组,接下来计算方案总数。

  • \(S\) 段左括号比右括号多 \(v\) 个。我们依次枚举 \(P\) 段长为 \(i\),左括号比右括号多 \(j\) 个,此时 \(Q\) 段长为 \(n-m-i\),右括号比左括号多 \(v+j\) 个。由于构造一个左括号比右括号多的字符串等同于构造一个右括号比左括号多的字符串,所以构造 \(Q\) 段的总方案数即 \(dp_{n-m-i,\space v+j}\)。则此时的答案为 \(dp_{i,j}*dp_{n-m-i,\space v+j}\)

  • 我们需要在该 \(P,S,Q\) 合法的情况时加上该贡献,只需要判断 \(P\)\(j\) 是否大于 \(S\) 段中最大的右括号比左括号多的数量即可,若满足则统计该贡献。

  • 总复杂度 \(O((n-m)^2)\)

完整代码

#include<bits/stdc++.h>
#define Size(x) ((int)(x).size())
#define int long long
#define Max(x,y) max((int)(x),(int)(y))
using namespace std;
const int N = 2e3+5,mod = 1e9+7,INF = 0x3f3f3f3f;
int dp[N][N];
inline void solve(){
	int n,m; cin >> n >> m;
	string s; cin >> s;
	int mx = -INF,tot = 0;	// S 段右括号比左括号多
	for(int i=0;i<m;i++){
		tot += s[i] == ')' ? 1 : -1;
		mx = max(mx,tot);
	}
	dp[0][0] = 1;
	for(int i=1;i<=2000;i++){
		dp[i][0] = dp[i-1][1];
		for(int j=1;j<=i;j++) 
			dp[i][j] = (dp[i-1][j-1] + dp[i-1][j+1])%mod;
	}
	int res = 0;
	for(int i=0;i<=n-m;i++){
		for(int j=0;j<=i;j++){	// j 为 P段左括号比右括号多
			if(j < mx || j-tot >n-m-i) continue;
			res = (res + dp[i][j]*dp[n-m-i][j-tot]%mod)%mod;
		}
	}
	cout << res << '\n';
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T; T = 1;
	while(T--) solve();
	return 0;
}

posted @ 2025-07-11 16:28  浅叶梦缘  阅读(13)  评论(0)    收藏  举报