Codeforces Global Round 25
D. Buying Jewels
题解
很容易被误导的题目,其实比较简单。
考虑到店铺越多越不好考虑,因此我们希望用尽可能少的店铺解决问题。这就比较简单了,我们将第一个店铺的珠宝价格设为 \(n - k - 1\),这样经过第一个店铺后,Alice 买了 \(1\) 件珠宝,还剩 \(k - 1\) 个硬币。此时我们将第 \(2\) 个店铺的珠宝价格设为 \(1\),那么 Alice 还会买 \(k - 1\) 件珠宝,此时 Alice 就恰好买了 \(k\) 件珠宝。
不过这种做法需要特判。如果 \(n < k\),即使价格设成 \(1\) 都无法买够 \(k\) 件珠宝,此时无解;如果 \(n = k\),那么只用将第一个店铺的价格设成 \(1\) 即可;如果 \(\displaystyle\frac n2 \leq k < n\),此时第 \(1\) 个店铺无法定价为 \(1\),假设第 \(1\) 个店铺定价为 \(p\),那么 Alice 最多只能买 \(\left\lfloor \displaystyle\frac np \right\rfloor + p\) 件商品,此时只有当 \(n = 2 \times k - 1\) 且 \(p = 2\) 时满足条件,其他情况均无解。
完整代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int T, n, k;
signed main(){
scanf("%lld", &T);
while(T--){
scanf("%lld%lld", &n, &k);
if(n == k)
printf("YES\n1\n1\n");
else if(k * 2 <= n)
printf("YES\n2\n%lld 1\n", n - k + 1);
else if(2 * k - 1 == n)
printf("YES\n2\n2 1\n");
else
printf("NO\n");
}
return 0;
}
E. No Palindromes
题解
猜结论好题,不过我们现在来严谨推导一下。
首先,如果字符串 \(s\) 全是一种字符,那么一定无解。如果一个字符串 \(s\) 本身就是非回文串,那么直接将一整个串输出即可。于是我们现在只用考虑 \(s\) 是回文串,且 \(s\) 中包含多种字符的情况。
其次,我们找到第一个前缀 \(s[1, k]\) 使得 \(s_1 != s_k\)(不妨设这个前缀为 \(aa \dots ab\)),假设 \(k \leq \displaystyle\frac n2\),那么如果 \(s[k + 1, n]\) 也不是回文串,那么这就是一种合法的划分,直接输出即可。
否则 \(s[k + 1, n]\) 就是一个回文串,而整个字符串也是一个回文串,因此我们可以通过 \(s[1, k]\) 求出 \(s[n - k + 1, n]\),再通过 \(s[n - k + 1, n]\) 求出 \(s[k + 1, 2 \times k]\),如此回环往复,我们就可以求出整个串 \(s\) 为 \(aa \dots abaa \dots ab \dots baa \dots abaa \dots a\)。
此时如果每段 \(a\) 的长度都是 \(1\),那么每次划分都有一边是回文串,这是无解的。如果 \(s\) 为 \(aa \dots abaa \dots a\),那么一定有一边会全是 \(a\),这也是无解的,否则直接将 \(k\) 右移一位即可得出一组合法的解。此时我们就发现,一个字符串要么无解,要么一定可以划分成两部分。于是跑一遍 Manacher,再将每个位置作为分界点尝试一遍即可。
完整代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 3e7 + 9;
int r[N], x, y, n, T;
char tmp[N], s[N];
int main(){
scanf("%d", &T);
while(T--){
x = y = 0;
scanf("%s", tmp + 1);
int len = strlen(tmp + 1);
n = 2 * len - 1;
s[0] = '@';
for(int i = 1; i < len; i++){
s[i * 2 - 1] = tmp[i];
s[i * 2] = '#';
}
s[n] = tmp[len];
s[n + 1] = '$';
for(int i = 1; i <= n; i++){
if(i > y){
while(s[y + 1] == s[2 * i - (y + 1)]){
++y;
x = i;
}
r[i] = y - i;
} else {
int ip = 2 * x - i;
if(i + r[ip] < y)
r[i] = r[ip];
else if(i + r[ip] > y)
r[i] = y - i;
else{
while(s[y + 1] == s[2 * i - (y + 1)]){
++y;
x = i;
}
r[i] = y - i;
}
}
}
if(r[len] < len - 1){
printf("Yes\n1\n");
for(int i = 1; i <= n; i += 2)
printf("%c", s[i]);
printf("\n");
continue;
}
bool flag = false;
for(int i = 1; i <= n - 2; i += 2){
if(r[(i + 1) / 2] == (i - 1) / 2)
continue;
if(r[(n + i + 2) / 2] == (n - i - 2) / 2)
continue;
printf("Yes\n2\n");
for(int j = 1; j <= n; j += 2){
printf("%c", s[j]);
if(j == i)
printf(" ");
}
printf("\n");
flag = true;
break;
}
if(!flag)
printf("No\n");
}
return 0;
}
F. Inversion Composition
题解
我们设 \(pos_i\) 表示排列 \(p\) 中值 \(i\) 的位置,我们又知道 \(p_i\) 表示 \(i\) 位置的值,因此 \(p_{pos_i} = i\)。
假设排列 \(q\) 中存在一个 \(i < j, q_i < q_j\),如果 \(pos_j < pos_i\),那么 \(q_{p_{pos_j}} = q_j > q_i = q_{p_{pos_i}}\),此时就会对答案造成 \(1\) 的贡献;如果 \(pos_j > pos_i\),此时就不会造成贡献。假设排列 \(p\) 中存在一个 \(i < j, q_i > q_j\),如果 \(pos_j < pos_i\) 此时就会对答案造成 \(1\) 的贡献;如果 \(pos_j > pos_i\),此时就会对答案造成 \(2\) 的贡献。
此时我们注意到排列 \(p\) 中的一个逆序对一定会对答案造成 \(1\) 的贡献,而一个顺序对会造成 \(0\) 或者 \(2\) 的贡献。于是我们先计算出 \(p\) 中的逆序对数量,将 \(k\) 减去这个值。此时 \(k\) 如果为奇数,小于 \(0\) 或大于顺序对的两倍就无解,否则一定存在一种构造方案(为了方便描述,下文的 \(k\) 为减去 \(p\) 中逆序对数量后除以 \(2\) 的值)。
注意到对于一对 \(i < j, pos_i < pos_j\),如果 \(q_i > q_j\),那么一定会造成 \(1\) 的贡献。于是我们从左往右,从大到小填数。假设我们枚举到了第 \(i\) 个位置,\(i\) 之前有 \(c\) 个位置的 \(pos > pos_i\),如果 \(2 \times c < k\),那么直接将 \(k\) 减去 \(c\);否则我们找到最小的 \(j\) 满足 \(j\) 之前有 \(k\) 个位置的 \(pos > pos_i\),将 \(q_{1 \sim j}\) 赋值成 \(i \sim i - j + 1\),将 \(q_{j + 1 \sim i - 1}\) 赋值成 \(i - j - 1 \sim 1\),此时答案就恰好为 \(k\),于是我们不能让 \(i\) 之后填的数造成贡献,因此我们将 \(q_{i + 1 \sim n}\) 赋值成 \(q_{i + 1 \sim n}\) 就可以了。时间复杂度 \(\mathcal O(n \log n)\)。
完整代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 9;
int t[N], p[N], pos[N], n, k, T;
int lowbit(int x){
return x & -x;
}
void insert(int i, int x){
for(; i <= n; i += lowbit(i))
t[i] += x;
}
int query(int i){
int res = 0;
for(; i > 0; i -= lowbit(i))
res += t[i];
return res;
}
signed main(){
scanf("%lld", &T);
while(T--){
scanf("%lld%lld", &n, &k);
for(int i = 1; i <= n; i++)
t[i] = pos[i] = 0;
for(int i = 1; i <= n; i++){
scanf("%lld", &p[i]);
pos[p[i]] = i;
k -= i - 1 - query(p[i]);
insert(p[i], 1);
}
if(k < 0 || k % 2 == 1){
printf("NO\n");
continue;
}
for(int i = 1; i <= n; i++)
t[i] = 0;
k /= 2;
bool flag = false;
for(int i = 1; i <= n; i++){
int now = query(pos[i]);
insert(pos[i], 1);
if(k <= now){
flag = true;
printf("YES\n");
if(k == 0){
for(int j = i; j >= 1; j--)
printf("%lld ", j);
for(int j = i + 1; j <= n; j++)
printf("%lld ", j);
printf("\n");
break;
}
int cnt = 0;
for(int j = 1; j <= i; j++){
if(pos[j] < pos[i])
cnt++;
if(cnt == k){
for(int l = i; l >= i - j + 1; l--)
printf("%lld ", l);
for(int l = i - j - 1; l >= 1; l--)
printf("%lld ", l);
printf("%lld ", i - j);
for(int l = i + 1; l <= n; l++)
printf("%lld ", l);
printf("\n");
break;
}
}
break;
} else
k -= now;
}
if(!flag)
printf("NO\n");
}
return 0;
}
G. Clacking Balls
题解
对于这类的停时问题,考虑给一个局面构造一个势能函数。如果 Alice 每进行一次操作,都会使这个势能函数减少 \(1\),那么我们只用将初始局面的势能减去终止局面的势能,就可以得出答案。
在这道题目中,我们考虑用相邻两个球之间的距离来设计势能函数。设 \(d_i = a_{i \bmod n + 1} - a_i\),那么当前局面的势能就是 \(\displaystyle\sum_{i = 1}^n f(d_i)\)。考虑到下一步操作有 \(n\) 种方法,那么势能的期望减少量就是 \(\displaystyle\frac 1n \displaystyle\sum_{i = 1}^n f(d_i) + f(d_{i \bmod n + 1}) - f(d_i - 1) - f(d_{i \bmod n + 1} + 1)\),根据刚才的思路,我们希望这个变化量为 \(1\)。
我们设 \(g(x) = f(x + 1) - f(x)\),那么 \(n = \displaystyle\sum_{i = 1}^n g(d_i - 1) - g(d_{i \bmod n} + 1) = \sum_{i = 1}^n g(d_i - 1) - \sum_{i = 1}^n g(d_{i \bmod n} + 1)\)。注意到一个数 \(d_i\) 会对前一项造成 \(g(d_i - 1)\) 的贡献,对后一项造成 \(g(d_i)\) 的贡献,因此等式可以改写成 \(n = \displaystyle\sum_{i = 1}^n g(d_i - 1) - g(d_i)\)。
通过比较人类智慧的观察,我们发现如果 \(g(x - 1) - g(x) = \displaystyle\frac nm x\) 的话,\(\displaystyle\sum_{i = 1}^n g(d_i - 1) - g(d_i) = \frac nm \sum_{i = 1}^n d_i = n\),是满足条件的。此时假设 \(g(0) = 0\),那么可以发现 \(g(x) = - \displaystyle\frac nm \binom{x + 1}{2}\) 是一个合法的 \(g\),那么倒推出 \(f(x) = - \displaystyle\frac nm \binom{x + 1}{3}\)。于是我们用初始状态的势能,减去终止局面的势能 \(f(m)\),就是答案了。
完整代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 9, MOD = 1e9 + 7;
int qpow(int a, int b){
int res = 1;
while(b > 0){
if(b & 1)
res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
int a[N], T, n, m, invm, inv6;
int f(int x){
return MOD - n * invm % MOD * (x + 1) % MOD * x % MOD * (x - 1) % MOD * inv6 % MOD;
}
signed main(){
inv6 = qpow(6, MOD - 2);
scanf("%lld", &T);
while(T--){
int ans = 0;
scanf("%lld%lld", &n, &m);
invm = qpow(m, MOD - 2);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
sort(a + 1, a + n + 1);
for(int i = 1; i < n; i++)
ans = (ans + f(a[i + 1] - a[i])) % MOD;
ans = (ans + f(m - (a[n] - a[1]))) % MOD;
printf("%lld\n", (ans - f(m) + MOD) % MOD);
}
return 0;
}
H. Thanos Snap
题解
首先,我们可以二分这个最大得分 \(mid\),将 \(a\) 序列中 \(\geq mid\) 的数设成 \(1\),其它数设成 \(0\),那么现在原问题就变成了进行 \(t\) 轮操作后,最终得到的序列中是否有 \(1\)。我们后续将得到的序列中一定有 \(1\) 称为有解
我们考虑如果进行了 \(dep\) 轮操作后问题有解,那么一个必要条件就是 \(1\) 的数量大于等于 \(2^{t - dep}\),不然 Bob 每次选择 \(1\) 数量较少的那一边往下递归,一定会让最终得到的序列全为 \(0\)。假设我们可以进行无数次交换操作,那么这个必要条件就变成了充要条件。于是我们考虑在交换次数上设计算法。
考虑到每次我们会将整个序列对半分开,于是我们考虑类似线段树的形式进行 DP。设 \(dp_i\) 表示线段树上的第 \(i\) 个节点表示的区间,为了让这个区间有解,最少需要将多少个 \(1\) 交换到第 \(i\) 个节点表示的区间。从下往上 DP,我们发现如果要让 \([l, r]\) 有解,必须让 \([l, mid]\) 和 \([mid + 1, r]\) 也有解,而操作 \([l, r]\) 区间会增加 \(1\) 次交换的机会,因此 \(dp_i\) 至少为 \(dp_{i \times 2} + dp_{i \times 2 + 1} - 1\)。
\([l, r]\) 有解还需要满足我们一开始推出的必要条件,就是 \([l, r]\) 中至少需要 \(2^{t - dep}\) 个 \(1\),假设这个区间已有 \(cnt\) 个 \(1\),那么还需要将 \(2^{t - dep} - cnt\) 个 \(1\) 交换进这个区间,因此 \(dp_i\) 还要对这个值取 \(\max\)。此时我们在 \(\mathcal O(n \log^2 n)\) 的时间复杂度内解决了这个问题。
完整代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 9;
int t[N << 2], dp[N << 2], sum[N], a[N], tar, T, n, k;
void build(int id, int l, int r, int dep){
t[id] = sum[r] - sum[l - 1];
if(dep == tar){
dp[id] = (t[id] == 0);
return;
}
int mid = (l + r) >> 1;
build(id << 1, l, mid, dep + 1);
build(id << 1 | 1, mid + 1, r, dep + 1);
dp[id] = max(max(0, dp[id << 1] + dp[id << 1 | 1] - 1), (1 << (tar - dep)) - t[id]);
return;
}
int main(){
scanf("%d", &T);
while(T--){
scanf("%d", &k);
n = (1 << k);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for(int i = 1; i <= k; i++){
tar = i;
int l = 1, r = N;
while(l <= r){
int mid = (l + r) >> 1;
for(int j = 1; j <= n; j++)
sum[j] = sum[j - 1] + (a[j] >= mid);
build(1, 1, n, 0);
if(dp[1] <= 0)
l = mid + 1;
else
r = mid - 1;
}
printf("%d ", l - 1);
}
printf("\n");
}
return 0;
}
本文来自博客园,作者:Orange_new,转载请注明原文链接:https://www.cnblogs.com/JPGOJCZX/p/19132043

浙公网安备 33010602011771号