逐月信息学 2024 提高组 #5
\(\color{black}\texttt{A. 党同伐异}\)
题目描述
有 \(N\) 个候选人,每个候选人都有一个不同的政治倾向 \(c_i\),进行 \(N-1\) 次选举。每轮选举中,所有未被淘汰的候选人给另一个没被淘汰的候选人。每一个候选人会将票投给 \(c_i\) 与自己差的绝对值最大的候选人。如果有多个这样的候选人,选择 \(c_i\) 更大的那个。每一轮中获得票最多的人淘汰,如果有多个这样的候选人,选择 \(c_i\) 更大的那个。问最后剩下哪个候选人。
思路
首先对 \(c_i\) 排序,因为每次淘汰的都是 \(c_i\) 最小或最大的人,所以剩余的候选人都是一个区间 \([l,r]\)。对于每一轮,二分最后一个 \(c_i - c_l\le c_r-c_i\) 的 \(i\),则 \([l,i]\) 内的人全部投票给 \(r\),\([i+1,r]\) 的人投票给 \(l\),比较一下即可。
空间复杂度 \(O(N)\),时间复杂度 \(O(N\log N)\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1000001;
struct Node {
int x, id;
}a[MAXN];
int n, s = 1, t;
int Binary_Search() {
int l = s, r = t;
for(; l < r; ) {
int mid = (l + r + 1) >> 1;
(a[mid].x - a[s].x <= a[t].x - a[mid].x ? l = mid : r = mid - 1);
}
return l;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
t = n;
for(int i = 1; i <= n; ++i) {
cin >> a[i].x;
a[i].id = i;
}
sort(a + 1, a + n + 1, [](const Node &a, const Node &b) { return a.x < b.x; });
for(int i = 1; i < n; ++i) {
int x = Binary_Search();
(x - s + 1 < t - x ? s++ : t--);
}
cout << a[s].id;
return 0;
}
总结
水题。
\(\color{black}\texttt{B. 混乱谜团}\)
题目描述
定义两个数字 \(x,y\) 在排列 \(A\) 中的距离为 \(|i-j|(A_i=x,A_j=y)\),一个排列 \(A\) 的混乱程度为 \(1,2\) 的距离、\(2,3\) 的距离、\(\dots\)、\(n-1,n\) 的距离之和。给定 \(N,K\),请构造一个长度为 \(N\),混乱程度为 \(K\) 的排列。
思路
我们先看一个混乱度为 \(N-1\) 的序列:\([1,2,3,\dots,N]\)。怎么让它的混乱度增加呢?
如果反转 \([2,3,\dots,N]\),混乱度加 \(N-2\)。
如果反转 \([3,4,\dots,N]\),混乱度加 \(N-3\);
\(\vdots\)
由于反转是相互独立的,所以我们可以枚举每种反转看是否需要。
空间复杂度 \(O(N)\),时间复杂度 \(O(N)\)。
代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 100001;
int n, a[MAXN];
ll k;
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> k;
a[1] = 1, k -= n - 1;
int l = 2, r = n;
for(int i = 2; l != r; ++i) {
if(n - i <= k) {
k -= n - i;
a[r] = i;
if(r > l) {
r--;
}else {
r++;
}
swap(l, r);
}else {
a[l] = i;
if(r < l) {
l--;
}else {
l++;
}
}
}
a[l] = n;
for(int i = 1; i <= n; ++i) {
cout << a[i] << " ";
}
return 0;
}
总结
比较简单,但要记得 long long
,而且考场上没做出来。
\(\color{black} \texttt{C. 固若金汤}\)
题目描述
有一排士兵,每个士兵都有一个默契值 \(c_i\)。现在要从中挑出一个子序列,使得子序列任意相邻三项按位与均不为 \(0\)。求最多能选多少士兵。
思路
定义 \(dp_{i,j}\) 表示子序列末尾选择 \(\dots,j,i\) 的最长长度。但这样的状态是 \(O(N^2)\) 的,转移是 \(O(N)\) 的,时间爆掉了。
由于状态不能优化,所以考虑优化转移:定义 \(f_{i,x}\) 表示以 \(i\) 结尾的末两个数按位且第 \(x\) 位为一的子序列长度最大值。
这个 \(f_{i,x}\) 在 dp 时处理就行了。这时,我们发现转移时就不需要 \(j\) 了,于是就 \(\texttt{AC}\) 了。
时空复杂度均为 \(O(N^2)\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2001;
int n, a[MAXN], dp[MAXN][MAXN], f[MAXN][32], ans = 1;
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i) {
cin >> a[i];
}
for(int i = 1; i <= n; ++i) {
dp[i][0] = 1;
}
for(int i = 1; i <= n; ++i) {
for(int j = 0; j < i; ++j) {
for(int k = 0; k < 32; ++k) {
if(!j || (((a[i] & a[j]) >> k) & 1)) {
f[i][k] = max(f[i][k], dp[i][j]);
}
}
ans = max(ans, dp[i][j]);
}
for(int j = i + 1; j <= n; ++j) {
for(int k = 0; k < 32; ++k) {
if((a[j] >> k) & 1) {
dp[j][i] = max(dp[j][i], f[i][k] + 1);
}
}
}
}
cout << ans;
return 0;
}
总结
这道题需要一些思考,考场上未做出。
\(\color{black}\texttt{D. 替身使者}\)
题目描述
在 \(\texttt{IOI}\) 中,\(\texttt{HPY}\) 十分有信心已经 \(\texttt{AK}\),但为了保险,他准备雇佣 \(N\) 个替身中的一些。每个替身都需要 \(x_i\) 元的费用,并且要求电脑至少升级到 \(k_i\) 级。\(\texttt{HPY}\) 的电脑原来是 \(0\) 级,每升一级需要 \(b\) 元。第 \(i\) 个替身会 \(m_i\) 道题,分别是 \(A_{i,1},A_{i,2},\dots,A_{i.m_i}\),问 \(\texttt{HPY}\) 至少需要多少钱来保证每道题都至少有一个替身会写?
若不可能则输出 \(-1\)。
思路
这道题如果去掉电脑升级就十分简单,所以我们来考虑如何去掉这个限制。
我们可以对每个人要求的级数进行排序,这样要求最大的一定是最后一个人。所以就可以省掉这个状态了。
dp:\(dp_{i,s}\) 表示在前 \(i\) 个中选,会的题目集合为 \(s\) 时的最少钱数。
加个降维优化就可以了。
空间复杂度 \(O(N+2^M)\),时间复杂度 \(O(N2^M)\)。
代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 101, MAXV = (1 << 21);
const ll INF = (ll)(2e18);
struct Node {
int x, k, res;
}s[MAXN];
int n, m, b;
ll dp[MAXV], ans = INF;
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m >> b;
for(int i = 1, k; i <= n; ++i) {
cin >> s[i].x >> s[i].k >> k;
for(int j = 1, x; j <= k; ++j) {
cin >> x;
s[i].res |= (1 << (x - 1));
}
}
sort(s + 1, s + n + 1, [](const Node &a, const Node &b) { return a.k < b.k; });
for(int i = 1; i < (1 << m); ++i) {
dp[i] = INF;
}
dp[0] = 0;
for(int i = 1; i <= n; ++i) {
for(int j = (1 << m) - 1; j >= 0; --j) {
dp[j | s[i].res] = min(dp[j | s[i].res], dp[j] + s[i].x);
}
ans = min(ans, dp[(1 << m) - 1] + 1ll * s[i].k * b);
}
cout << (ans == INF ? -1 : ans);
return 0;
}
总结
需要注意 long long
,\(-1\) 等问题。