题目来源
https://codeforces.ml/contest/1443
A. Kids Seating
在 [1, 4n]这个区间内寻找n个数字,使得这n个数两两不互质,两两不互相可以整除。
思路分析
从2n+2开始,每隔2取一个数字即可。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 7;
int a[maxn];
vector <int> ans;
bool vis[maxn];
int main(){
int t; scanf("%d", &t);
while (t --){
int n; scanf("%d", &n);
int tmp = 4 * n;
for (int i=1; i<=n; i++){
printf("%d ", tmp);
tmp -= 2;
}
printf("\n");
}
return 0;
}
B. Saving the City
给一个数字序列。有两种操作,将某一位置上的'0'变成'1',这一步需要$b$费用;第二种操作是将一段连续的‘1’变成'0',这一步需要a费用。问最后将所有的数组都变成'0'的最小费用。
思路分析
统计两段‘1’序列之间的‘0’的个数p,比较p * b 和 a大小。即对于一段0序列,判断与前后一起消去以及前后两段1序列各自消去的费用的最小值。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 7;
char ch[maxn];
int main(){
int t; scanf("%d", &t);
while (t --){
int a, b; scanf("%d%d", &a, &b);
int ans = 0;
bool flag = false;
scanf("%s", ch);
int ln = (int)strlen(ch);
int num = 0;
for (int i=0; i<ln; i++){
if (ch[i] == '1'){
if (!flag) ans += a;
flag = true;
ans += min(a, b * num);
num = 0;
}else if (flag) num ++;
}
printf("%d\n", ans);
}
return 0;
}
C. The Delivery Dilemma
有n家餐厅,有两种派送方式,第一种是他配送,需要a[i]时间;第二种是自己去取,需要b[i]时间。若多家餐厅选择方式二进行派送,则需要自己一家一家取。问最少的时间费用是多少。
思路分析
可以想到的是,答案的取决于最大的派送时间,以及自己取的时间和。根据商家派送时间进行排序,然后贪心枚举每一次两个时间即可。
#include <bits/stdc++.h>
#define int long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 2e5 + 7;
//int a[maxn], b[maxn];
struct node{
int a, b;
};
node tt[maxn];
bool cmp(const node& u, const node& v){
if (u.a != v.a) return u.a > v.a;
else return u.b > v.b;
}
signed main(){
int t; scanf("%lld", &t);
while (t --){
int n; scanf("%lld", &n);
for (int i=1; i<=n; i++) scanf("%lld", &tt[i].a);
for (int i=1; i<=n; i++) scanf("%lld", &tt[i].b);
sort(tt+1, tt+1+n, cmp);
int mmax = tt[1].a;
int ans = 0, res = INF;
for (int i=1; i<=n; i++){
mmax = tt[i].a;
// cout << res << " " << mmax << " " << ans << endl;
res = min(res, max(mmax, ans));
ans += tt[i].b;
}
res = min(res, ans);
printf("%lld\n", res);
}
return 0;
}
D. Extreme Subtraction
给定一个数字序列,每一次可以从头选择一个连续区间进行-1, 也可以从尾部选择一个连续区间进行-1,问最后能否在多次操作之后,使得整个数字序列变成0。
思路分析
给区间进行一个增加减少的操作,可以联想到差分的思想。然后按照差分的思想就能找到最终的解法。
#include <bits/stdc++.h>
#define int long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 2e5 + 7;
int a[maxn];
int p[maxn];
signed main(){
int t; scanf("%lld", &t);
while (t --){
int n; scanf("%lld", &n);
for (int i=1; i<=n; i++) scanf("%lld", &a[i]);
for (int i=1; i<=n; i++) p[i] = a[i] - a[i-1];
int ans = 0;
for (int i=2; i<=n; i++) if (p[i] < 0) a[1] += p[i];
if (a[1] >= 0) printf("YES\n");
else printf("NO\n");
}
return 0;
}
E. Long Permutation
给定一个全排列的长度n,表示为1到n的一个全排列。有两种操作。第一种操作是询问此时排列中,[l, r]这个区间的区间和为多少。另一种操作为将此时的全排列向后递增x次。
第二种的操作具体可以表现为,当n=3时,第一组全排列为 1 2 3 。 第二组为 1 3 2。依次类推。
思路分析
首先区间和可以使用线段树去实现。而对于第二种操作,根据数据范围可以想到的是他最多只会改变最后的14个数字。然后就可以根据逆康拓展开,求出此时的全排列,然后逐一的进行单点修改。
#include <iostream>
#include <queue>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <map>
#define int long long
using namespace std;
const int maxn = 2e5 + 7;
int a[maxn];
int frac[maxn], tree[maxn << 2];
void init(){
frac[0] = 1;
int p = 0;
for (int i=1; i<maxn; i++){
frac[i] = frac[i-1] * i;
p = i;
if (frac[i] > 2e10) break;
}
// cout << "P: " << p << endl;
}
void build(int p, int l, int r){
if (l == r){
tree[p] = a[l]; return;
}
int mid = l + r >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
int ask(int p, int l, int r, int L, int R){
if (L <= l && r <= R) return tree[p];
int mid = l + r >> 1;
int ans = 0;
if (mid >= L) ans += ask(p << 1, l, mid, L, R);
if (mid < R) ans += ask(p << 1 | 1, mid+1, r, L, R);
return ans;
}
void update(int p, int l, int r, int x, int y){
if (l == r){
tree[p] = y; return;
}
int mid = l + r >> 1;
if (mid >= x) update(p << 1, l, mid, x, y);
else if (mid < x) update(p << 1 | 1, mid + 1, r, x, y);
tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
void decantor(int x, int n, int m)
{
vector<int> v;
vector<int> _a;
for(int i=1;i<=n;i++)
v.push_back(i);
for(int i=n;i>=1;i--)
{
int r = x % frac[i-1];
int t = x / frac[i-1];
x = r;
sort(v.begin(),v.end());
_a.push_back(v[t]);
v.erase(v.begin()+t);
}
// int ln = (int)_a.size();
int p = m - n + 1;
for (int i=p; i<=m; i++){
a[i] = _a[i - p];
update(1, 1, m, i, _a[i-p] + p - 1);
}
}
int num = 0;
signed main(){
init();
int n, q;
scanf("%lld%lld", &n, &q);
for (int i=1; i<=n; i++) a[i] = i;
build(1, 1, n);
while (q --){
int op; scanf("%lld", &op);
if (op == 1){
int l, r; scanf("%lld%lld", &l, &r);
printf("%lld\n", ask(1, 1, n, l, r));
}else{
int x; scanf("%lld", &x);
num += x;
if (n >= 15) decantor(num, 15, n);
else decantor(num, n, n);
}
}
return 0;
}
F. Identify the Operations
给定一个长度为n的a数组,其中所有数字是从1到n,各不相同。再给定一个b数组,表示需要筛选出来的数组。每一次操作,是在a数组中寻找一个位置p,将其两边的数字中的某一个数字筛选出来,并将p号位置上的数字删去。问有多少种操作排列组合能够最终筛选最终答案。
注:a, b数组中的数都各不相同。
思路分析
对于选出一个数字放入到最终的答案队列中,需要将这个数字两边的某一个数字删去。而我们不能删去b数组后面的数。
最终答案为过程中,各个数字筛选出来的结果数的乘积。而不能删选的结果数字为后续需要被筛选出来的那些数字。而对于被删掉的数字,其实不需要标记,因为当一个数字删除之后,其前后的位置会重新顶上来,这个位置也就被重新激活。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 2e5 + 7;
const int mod = 998244353;
int a[maxn], b[maxn], p[maxn];
bool vis[maxn];
signed main(){
int t; scanf("%lld", &t);
while (t --){
int n, k; scanf("%lld%lld", &n, &k);
for (int i=1; i<=n; i++) vis[i] = false;
vis[0] = true; vis[n+1] = true;
for (int i=1; i<=n; i++){
scanf("%lld", &a[i]);
p[a[i]] = i;
}
for (int i=1; i<=k; i++){
scanf("%lld", &b[i]);
vis[p[b[i]]] = true;
}
int ans = 1;
for (int i=1; i<=k; i++){
int tmp = 0;
if (!vis[p[b[i]] + 1]) tmp ++;
if (!vis[p[b[i]] - 1]) tmp ++;
vis[p[b[i]]] = false;
ans = ans * tmp % mod;
}
printf("%lld\n", ans);
}
return 0;
}