Codeforces Round #751 (Div. 2)
Dashboard - Codeforces Round #751 (Div. 2) - Codeforces
A. Two Subsequences
思路
- 贪心
因为 b b b 无所谓,因此找到最小的字符给 a a a 即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
string str; cin >> str;
char c = 'z';
int id = -1;
for (int i = 0; i < str.size(); i ++)
if (str[i] <= c) c = str[i], id = i;
cout << c << ' ';
for (int i = 0; i < str.size(); i ++)
if (i != id) cout << str[i];
cout << '\n';
}
return 0;
}
B. Divine Array
思路
-
模拟
观察可知,操作一定不会进行很多次,因此模拟记录一下历史版本,然后直接读入输出即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[2010][2010], b[2010];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[0][i];
int id = 0;
while (true) {
vector<int> cnt(n + 1);
for (int i = 1; i <= n; i ++) cnt[a[id][i]] ++;
for (int i = 1; i <= n; i ++) b[i] = cnt[a[id][i]];
bool ok = true;
for (int i = 1; i <= n; i ++)
if (b[i] != a[id][i]) {
ok = false;
break;
}
if (ok) break;
id ++;
for (int i = 1; i <= n; i ++) a[id][i] = b[i];
}
cin >> m;
while (m --) {
int x, y; cin >> x >> y;
cout << a[min(id, y)][x] << '\n';
}
}
return 0;
}
C. Array Elimination
思路
-
贪心
显然我们要遍历 k ∈ [ 1 , n ] k\in [1,n] k∈[1,n] 的每一种情况,对于这种和二进制相关的操作我们直接 观察二进制即可,操作等价将 k k k个数二进制共有的为 1 1 1的位减去,因此如果想让 k k k 成立,则对于 [ 0 , 30 ) [0,30) [0,30)的每一位 1 1 1出现的次数可以被 k k k整除才行,因此直接暴力判断即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
vector<int> cnt(30);
cin >> n;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
for (int j = 0; j < 30; j ++)
if (a[i] >> j & 1)
cnt[j] ++;
}
vector<int> res;
for (int i = 1; i <= n; i ++) {
bool ok = true;
for (int j = 0; j < 30; j ++)
if (cnt[j] % i) {
ok = false;
break;
}
if (ok) res.push_back(i);
}
for (auto t : res) cout << t << ' ';
cout << '\n';
}
return 0;
}
D. Frog Traveler
题意
青蛙在 n n n 米深的井中。
对于每一个深度,有两个量 a i a_i ai和 b i b_i bi。
a i a_i ai 表示在深度为 i i i米的时候可以往上跳的最高高度,就是说在深度为 i i i米的地方可以往上跳 [ 0 , a i ] [0, a_i] [0,ai]米。
b i b_i bi 表示在深度为 i i i米的地方时会往下滑 b i b_i bi米。
青蛙每跳一次,就会下滑一次。
请求出青蛙最少跳几次可以跳出井(深度为 0 0 0 米)。
思路
-
线段树优化 d p dp dp
设 f [ i ] f[i] f[i]:跳到 i i i米深所需的最小步数,我们考虑暴力 d p dp dp,对于任意一个 f [ i ] f[i] f[i] 可以转移到 [ i − a i , i ] [i-a_i,i] [i−ai,i] 这个范围一个 j j j 且再向下滑落 a j a_j aj 这个位置,暴力的话是 O ( n 2 ) O(n^2) O(n2)的。
由于转移带有区间赋值,因此考虑用线段树优化,由于有下滑这个操作不是很好区间赋值,因此考虑我们人为的将它做了,现在将跳跃下滑 分成两部分, f [ i ] f[i] f[i]:跳到 i i i米深且未下滑所需的最小步数,最终我们的答案就是 f [ 0 ] f[0] f[0],那么怎么转移呢?
对于 i i i 来说,先将 i i i下滑一下即到 i + b i i+b_i i+bi,然后再向上跳,这个时候就可以支持完整的区间复制了,即将 [ i + b i − a i + b i , i + b i ] [i+b_i-a_{i+b_i},i+b_i] [i+bi−ai+bi,i+bi] 用 f [ i ] + 1 f[i]+1 f[i]+1更新一下,由于还需要输出方案,因此线段树转移的时候记录一下即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 4e5 + 10, M = 2 * N, INF = 2e9, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N], b[N];
vector<int> in[N];
struct Node {
int l, r;
int mx, tag;
int to;
}tr[N << 2];
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
void down(int u) {
if (tr[u].tag != INF) {
tr[ls(u)].mx = min(tr[ls(u)].mx, tr[u].tag);
tr[rs(u)].mx = min(tr[rs(u)].mx, tr[u].tag);
tr[ls(u)].tag = min(tr[ls(u)].tag, tr[u].tag), tr[rs(u)].tag = min(tr[rs(u)].tag, tr[u].tag);
if (tr[ls(u)].mx == tr[u].tag) tr[ls(u)].to = tr[u].to;
if (tr[rs(u)].mx == tr[u].tag) tr[rs(u)].to = tr[u].to;
tr[u].tag = INF;
}
}
void build(int u, int l, int r) {
tr[u] = {l, r, INF, INF, INF};
if (l == r) return ;
int mid = l + r >> 1;
build(ls(u), l, mid), build(rs(u), mid + 1, r);
}
void modify(int u, int l, int r, int x, int to) {
if (l > r) return ;
if (l <= tr[u].l && tr[u].r <= r) {
tr[u].mx = min(tr[u].mx, x);
tr[u].tag = min(tr[u].tag, x);
if (tr[u].mx == x) tr[u].to = to;
return ;
}
down(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(ls(u), l, r, x, to);
if (r > mid) modify(rs(u), l, r, x, to);
}
Node query(int u, int x) {
if (tr[u].l == tr[u].r) return tr[u];
down(u);
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) return query(u << 1, x);
else return query(u << 1 | 1, x);
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1; i <= n; i ++) cin >> b[i];
build(1, 0, n);
modify(1, n - a[n], n, 1, n);
// cout << query(1, 2).mx << '\n';
for (int i = n - 1; i >= 1; i --) {
int p = i + b[i];
int f = query(1, i).mx;
modify(1, p - a[p], p, f + 1, i);
}
int v = query(1, 0).mx;
if (v == INF) cout << -1 << '\n';
else {
cout << v << '\n';
vector<int> res;
int id = 0;
while (true) {
res.push_back(id);
if (id == n) break;
id = query(1, id).to;
}
res.pop_back();
reverse(res.begin(), res.end());
for (auto t : res) cout << t << ' ';
cout << '\n';
}
return 0;
}
E. Optimal Insertion
题意
给定两个序列 $a,b$,长度分别为$n,m$。接下来将$b$中的所有元素以**任意方式**插入序列$a$ 中**任意位置**,请找出一种插入方式使结果序列中的逆序对数量最小化,并输出这个最小值。
思路
-
贪心、分治
首先贡献分成三类: a a a中本身存在的, b b b中本身存在的, a , b a,b a,b之间产生的。
考虑 b b b中本身存在的,猜测为 0 0 0,即将 b b b数组按值从小到大的顺序依次放入 a a a中最优,证明:
如果上述不是不最优,设最终放完的数组为 c c c 且长度为 n + m n + m n+m,则有 c i ∈ b , c j ∈ b , i < j c_i\in b,c_j\in b,i<j ci∈b,cj∈b,i<j 且 c i > c j c_i>c_j ci>cj, 交换 c i , c j c_i,c_j ci,cj后,对于任意 k < i , k > j k<i,k>j k<i,k>j和这两个点构成的贡献不变,对于 i < k < j i<k<j i<k<j的 c k c_k ck,如果 c k > c i ∣ ∣ c k < c j c_k>c_i||c_k<c_j ck>ci∣∣ck<cj 贡献不变,如果 c j < c k < c i c_j<c_k<c_i cj<ck<ci 贡献减二,因此将 b b b数组按照从小到大的顺序插入 a a a中是最优的。
接下来考虑怎么让 a , b a,b a,b之间产生的逆序对最小,假设 b b b 依次插入的位置为 p 1 , p 2 , . . . , p m p_1,p_2,...,p_m p1,p2,...,pm且 p m = i p_m=i pm=i代表插到 i i i的前一个位置是最优的(CF1601C Optimal Insertion - yxh050917 的博客 - 洛谷博客 (luogu.com.cn))这个佬给了证明,则考虑分治将 b b b数组的 [ l 1 , r 1 ] [l_1,r_1] [l1,r1]插入 a a a数组的 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]中,每次暴力填 l 1 + r 1 2 \frac{l_1+r_1}{2} 2l1+r1这个 b b b即可,由于 [ 1 , l 2 ) , ( r 2 , n ] [1,l_2),(r_2,n] [1,l2),(r2,n]的贡献是定值,因此将 b m i d b_{mid} bmid和 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]这个区间里的 a a a构成最小的逆序对即可。
最后随便暴力统计一下逆序对即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N], b[N];
vector<int> ve;
int find(int x) {
return lower_bound(ve.begin(), ve.end(), x) - ve.begin() + 1;
}
int pre[N], suf[N];
int pos[N];
void dfs(int l1, int r1, int l2, int r2) {
if (l1 > r1) return ;
for (int i = l2 - 1; i <= r2 + 1; i ++) pre[i] = suf[i] = 0;
int mid = l1 + r1 >> 1;
for (int i = l2; i <= r2; i ++)
pre[i] = pre[i - 1] + (a[i] > b[mid]);
for (int i = r2; i >= l2; i --)
suf[i] = suf[i + 1] + (a[i] < b[mid]);
int p = l2;
for (int i = l2; i <= r2; i ++)
if (pre[i - 1] + suf[i] < pre[p - 1] + suf[p]) p = i;
pos[mid] = p;
dfs(l1, mid - 1, l2, p), dfs(mid + 1, r1, p, r2);
}
int tr[N];
int cnt;
void add(int x) {
for (; x <= cnt; x += x & -x) tr[x] ++;
}
int sum(int x) {
int res = 0;
for (; x; x -= x & -x) res += tr[x];
return res;
}
vector<int> g[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin >> n >> m;
ve.clear();
for (int i = 1; i <= n; i ++) cin >> a[i], ve.push_back(a[i]);
for (int i = 1; i <= m; i ++) cin >> b[i], ve.push_back(b[i]);
sort(b + 1, b + 1 + m);
sort(ve.begin(), ve.end()), ve.erase(unique(ve.begin(), ve.end()), ve.end());
dfs(1, m, 1, n + 1);
cnt = ve.size();
int id = m;
for (int i = n + 1; i; i --) {
g[i].clear();
if (i <= n) g[i].push_back(find(a[i]));
while (pos[id] == i) g[i].push_back(find(b[id --]));
}
for (int i = 1; i <= cnt; i ++) tr[i] = 0;
LL res = 0;
for (int i = n + 1; i; i --) {
for (auto t : g[i])
res += sum(t - 1), add(t);
}
cout << res << '\n';
}
return 0;
}
F. Difficult Mountain
题意
n
n
n 个人相约去爬山。
山的初始攀登难度为
d
d
d。
每位登山者有两个属性:技巧
s
s
s和整洁度
a
a
a。
技巧为 s s s 的登山者能登上攀登难度为 p p p的山当且仅当 p ≤ s p\le s p≤s。
在一位整洁度为 a a a 的登山者登上攀登难度为 p p p的山后,山的攀登难度会变为 m a x ( p , a ) max(p,a) max(p,a)。
请给这些登山者指定一个爬山的先后顺序,最大化登上山的人数。
如果轮到一位登山者时他能登上山,则他一定会选择登山。
思路
-
贪心
将初始 s i < d s_i<d si<d的均去除掉,然后按照 m a x ( s i , a i ) max(s_i,a_i) max(si,ai)从小到大排序,如果相同则按照 s i s_i si从小到大排序,可以证明按照这个顺序来登山的话是最优的。
证明如下:
假设到了第 i i i个人且当前山高为 d d d则有:
-
m a x ( a i , s i ) = s i max(a_i,s_i)=s_i max(ai,si)=si,由于 d ≤ m a x ( a j , j ∈ [ 1 , i − 1 ] ) ≤ s i d\le max(a_j,j\in[1,i-1])\le s_i d≤max(aj,j∈[1,i−1])≤si,因此当前这个这个人一定可以登山,那么当前这个人登山是否是最优的呢?由于前面的都已经选了,因此只可能影响 j ∈ [ i + 1 , n ] j\in [i+1,n] j∈[i+1,n]的人的登山情况,分类来:
- 看如果 j ∈ [ i + 1 , n ] , s j = m a x ( s j , a j ) j\in [i+1,n],s_j=max(s_j,a_j) j∈[i+1,n],sj=max(sj,aj)均成立的话,由于按从小到大排的序,因此后面的一定可以选,所以没有影响,
- 否则一定存在一个位置 k ∈ [ i + 1 , n ] , a k = m a x ( s k , a k ) k\in [i+1,n],a_k=max(s_k,a_k) k∈[i+1,n],ak=max(sk,ak),那么 [ i + 1 , k − 1 ] [i+1,k-1] [i+1,k−1]类比于情况 1 1 1一定可以都选,假设 d = a i > s k d=a_i>s_k d=ai>sk,则如果要想要 k k k 登山的话,至少要删掉 i i i,且 d d d 还可能会变大,因此对于这种情况选 i i i 也是较优的
-
m a x ( a i , s i ) = a i max(a_i,s_i)=a_i max(ai,si)=ai,这个时候如果 s i > d s_i>d si>d的话也直接选,证明类似于上面
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 5e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
PII a[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> a[i].x >> a[i].y;
sort(a + 1, a + 1 + n, [&](PII a, PII b) {
if (max(a.x, a.y) == max(b.x, b.y)) return a.x < b.x;
return max(a.x, a.y) < max(b.x, b.y);
});
int res = 0;
for (int i = 1; i <= n; i ++)
if (m <= a[i].x) {
m = max(m, a[i].y);
res ++;
}
cout << res << '\n';
return 0;
}
// 能登的 a 最小的
本文提供了Codeforces Round #751 (Div.2)比赛中的六道题目(A-F)的解题思路及代码实现,涉及贪心算法、模拟、线段树优化DP等多种算法和技术。
1600

被折叠的 条评论
为什么被折叠?



