文章目录
Codeforces Round #681 (Div. 2, based on VK Cup 2019-2020 - Final)
A. Kids Seating
题意: 给定n,从1~n*4中选出 n个数,使得任意两个数不互质且没有整除关系。
题解: 看到互质关系,想到间隔2取数,看到整除关系,就想到尽可能让数字靠向4n。那么直接取4n - 2, 4n - 4, …, 2n
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;
int n, m, T;
int main() {
// freopen("in.txt", "r", stdin);
cin >> T;
while(T--) {
cin >> n;
int cur = 4 * n - 2;
for (int i = 1; i <= n; ++i) {
cout << cur << " ";
cur -= 2;
}
cout << endl;
}
return 0;
}
B. Saving the City
题意: 给一段序列,1为有炸弹,0为无炸弹,要引爆所有炸弹,同时一个炸弹可以触发前后两个的炸弹。引爆一个炸弹的钱是a,放一个炸弹的钱是b。问引爆所有炸弹的最少花费。字符串长度为 1 0 5 10^5 105, 1 < = a , b < = 1000 1<=a,b<=1000 1<=a,b<=1000
题解: 如果想要把所有的炸弹引爆,那么要不然不按照炸弹。如果要安装炸掉,必然是先装0少的地方。每次填满一段0,就会使得1的连续数目段减一。所有只需要把0的段长度截出来,然后按照从小到大排序,不断往里面填炸掉。因此答案的为: m i n { a ∗ c n t 1 , a ∗ ( c n t 1 − 1 ) + c n t 0 [ 1 ] ∗ b , a ∗ ( c n t 1 − 2 ) + ( c n t 0 [ 1 ] + c n t 0 [ 2 ] ) ∗ b , . . . } min\{a* cnt_1, a*(cnt_1 - 1) + cnt_0[1] * b, a*(cnt_1-2)+(cnt_0[1]+cnt_0[2])*b,...\} min{a∗cnt1,a∗(cnt1−1)+cnt0[1]∗b,a∗(cnt1−2)+(cnt0[1]+cnt0[2])∗b,...}。需要不断抠出来1的段和0的段。
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;
int n, m, T;
int v[N], sz[N];
int main() {
// freopen("in.txt", "r", stdin);
cin >> T;
while(T--) {
memset(v, 0, sizeof v);
memset(sz, 0, sizeof sz);
cin >> n >> m;
string s;
cin >> s;
int cnt = 0;
for (int i = 0, j = 0; i < s.size(); i = j) {
j = i;
while(j < s.size() && s[i] == s[j]) j++;
v[cnt] = s[i] - '0';
sz[cnt] = j - i;
cnt++;
}
// cout << 11 << endl;
vector<int> vec;
int cnt_one = 0;
for (int i = 0; i < cnt; ++i)
if (v[i] == 0) {
if (i && i < cnt - 1) vec.push_back(sz[i]);
}
else cnt_one++;
// cout << 2 << endl;
sort(vec.begin(), vec.end());
LL res = (LL)n * cnt_one;
// cout << res << endl;
LL sum = 0;
for (int i = 0; i < vec.size(); ++i) {
sum += vec[i];
res = min(res, sum * m + (--cnt_one) * n);
}
cout << res << endl;
}
return 0;
}
C. The Delivery Dilemma
题意: 给定a数组和b数组,对于1 ~ n的位置,如果选择a数组,那么花费为a数组中被选择的最大值;如果选择b数组,那么花费为b数组中被选择的累加值。求最小的答案
题解: 要让答案尽可能小,我们可以让a和b数组按照a数组从小到大排序,然后答案的情况必然包含在[min(max(a1, a2, …, an)), min(max(a1, a2, …an-1), bn), min(max(a1, a2, …, an-2), bn + bn-1)]。因此只需要排完序后扫描一遍即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;
int n, m, T;
PII point[N];
int main() {
// freopen("in.txt", "r", stdin);
cin >> T;
while(T--) {
scanf("%d", &n);
for (int i = 0; i < n; ++i) cin >> point[i].first;
for (int i = 0; i < n; ++i) cin >> point[i].second;
sort(point, point + n);
LL res = 1e9 + 10, sum = 0;
for (int i = n - 1; i >= 0; --i) {
res = min(res, max(sum, (LL)point[i].first));
sum += point[i].second;
}
res = min(res, sum);
printf("%lld\n", res);
}
return 0;
}
D. Extreme Subtraction
题意: 给定一个序列,长度为n,有2种操作,操作1可以使得从1 ~ k的数字都减一, 操作2可以使得从k ~ n的数字都减一1.问能否通过有限次操作,使得整个数列变为0.
题解: 最后使得整个数列变为0,可以规约为是否能够整个数列变为同一个数字,那么就是要让整个数列变为最小的那个数字。因此可以维护一个差分数组b,记录一下当前的位置需要减去多少,如果当前的a[i] < a[i + 1],那么就是要让 i + 1 ~ n减去a[i + 1] - a[i];如果a[i] > a[i + 1],那么就是要让1 ~ i减去a[i] - a[i + 1]。然后求个b数组的前缀即可知道每个数字最少要减去多少,与a[i]判断大小即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;
int n, m, T;
int a[N];
LL b[N];
int main() {
cin >> T;
while(T--) {
memset(b, 0, sizeof b);
cin >> n;
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1; i < n; ++i) {
if (a[i] > a[i + 1]) {
b[1] += a[i] - a[i + 1];
b[i + 1] -= a[i] - a[i + 1];
}
else {
b[i + 1] += a[i + 1] - a[i];
}
}
int flg = 0;
for (int i = 1; i <= n; ++i) {
b[i] += b[i - 1];
if (b[i] > a[i]) {
flg = 1;
printf("NO\n");
break;
}
}
if (!flg) printf("YES\n");
}
return 0;
}
E. Long Permutation
题意: 给定一个序列[1, 2, 3, …, n], 序列长度为1e5。有1e5个操作,分别为:操作1:1 l r,即计算序列区间[l, r]的区间和;操作2 :2 k,找出当前序列往后字典序第k大。1 <= k <= n。
题解: 由于1 <= k <= n,因此就算执行1e5次操作2,那么也只会是找序列的1e10个。而14! > 1e10,因此无论执行多少次操作,本质上只会改变序列的最后14位数字。所以,对于前缀和,可以直接暴力维护,然后对于每次操作2,求出最后14位数字的序列,然后暴力修改前缀和数组sum即可。
这里有一个trick,求[1, 2, 3, …, n]的后面第k个序列可以直接康托展开求解,但是如果求[2, 3, 1, …, n , n - 1]这样的序列后面的第k个是无法直接逆康托展开的。然而本题的序列是从[1, 2, 3, …, n]开始的,我们可以维护一个x,记录一下当前操作2需要求解的序列是相对于[1, 2, 3,…,n]的第几个,即每次都是x = x + k。然后求解最后14个数字的逆康托展开,可以先求出[1, 2, …, 14]往后的第k个,然后加上偏移量offset = n - 14。
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;
int n, m, T, q;
int a[N], c[100];
LL sum[N], fact[100];
int lowbit(int x) {
return x & (-x);
}
// 单点修改
void add(int x, int y) {
for (int i = x; i <= m; i += lowbit(i)) c[i] += y; // 不断往父节点跳
}
int kth(int k) {
int res = 0; // res记录小于k的最后一个位置
for(int i = m >> 1; i; i >>= 1) // 以2进制逼近
if(c[res + i] < k) {
res += i;
k -= c[res];
}
return res + 1; // 因为返回的是前一个位置
}
// 逆康托展开: 2 -> [1, 4, 2, 3]
vector<int> decode(LL x) {
m = 1;
while(m <= min(14, n)) m <<= 1; // m为大于n的第一个2的幂次方,这是利用了树状数组c数组处于2的幂次方时,c[i]相当于query(i)的性质,以此来优化二分
memset(c, 0, sizeof c);
vector<int> res; // res存储答案序列
for(int i = 1; i <= min(14, n); ++i) add(i, 1); // 1表示没有使用,0表示使用过
for(int i = min(14, n) - 1; i >= 0; --i) { // 按照逆康托展开的公式处理
int t = kth(x / fact[i] + 1); // 类似二分的思想
res.push_back(t);
add(t, -1);
x %= fact[i];
}
return res;
}
int main() {
cin >> n >> q;
fact[0] = 1;
for (int i = 1; i <= n; ++i) {
a[i] = i, sum[i] = sum[i - 1] + i;
if (i <= 14) fact[i] = fact[i - 1] * i;
}
int offset = 0; // 计算偏移量
if (n > 14) offset = n - 14;
LL k = 0;
for (int i = 1, op, l, r; i <= q; ++i) {
scanf("%d", &op);
if (op == 1) {
scanf("%d%d", &l, &r);
printf("%lld\n", sum[r] - sum[l - 1]);
}
else {
LL x;
scanf("%lld", &x);
k += x;
vector<int> res = decode(k); // 求出最后14的数字的逆康托展开
for (int j = max(1, n - 13), idx = 0; j <= n; ++j) {
sum[j] = sum[j - 1] + res[idx++] + offset; // 每个数字实际上是res[idx]+offset
}
}
}
return 0;
}
F. Identify the Operations
题意: 给定一个长度为n的数组a,每次可以选择一个位置的数字删除,然后把它的左边或者右边元素加入到b数组的结尾。现在问从a数组构造b数组有多少种方案
题解: 对于b数组内的元素分析,b数组内的第一个数字,必然是第一次被加入的,因此需要扫描b数组来做判断。对于每一个b数组内的元素,如果当前元素被加入b数组,那么当前元素的相邻2个元素之一必然被删除,而这2个元素如果存在与还没有被扫描到的b数组中,那么不能被删除。所以对于每一个b[i],可以判断下其在a数组对应位置相邻两个位置的元素是否也处于b数组内,如果处于,那么这个数字不能被删除。每次只需要把可以删除的位置数目累成到方案中即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int const N = 2e5 + 10, M = 998244353 ;
typedef long long LL;
typedef pair<int, int> PII;
int n, m, T;
int a[N], b[N], mp1[N], mp2[N];
int main() {
// freopen("in.txt", "r", stdin);
cin >> T;
while(T--) {
for (int i = 1; i <= n; ++i) mp1[i] = mp2[i] = 0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), mp1[a[i]] = i;
for (int i = 1; i <= m; ++i) scanf("%d", &b[i]), mp2[b[i]] = 1;
LL res = 1;
mp2[0] = 1, a[0] = a[n + 1] = 0;
for (int i = 1; i <= m; ++i) {
int k = 2;
if (mp2[a[mp1[b[i]] - 1]]) k--;
if (mp2[a[mp1[b[i]] + 1]]) k--;
res = res * k % M;
// cout << res << endl;
if (!res) break;
mp2[b[i]] = 0;
}
printf("%d\n", res);
}
return 0;
}