Codeforces Round #602 (Div. 2, based on Technocup 2020 Elimination Round 3)
A - Math Problem
题意:给n条线段[l,r],求再加一条可以退化成点的线段,与所有线段各至少有一个公共点。
题解:求出最右边的左端点和最左边的右端点,左端点>右端点,则说明你的这个线段要把这两个连起来,否则你的线段就是现在的[左端点,右端点]上的随便一个点。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
void test_case() {
int n;
scanf("%d", &n);
int L = 1, R = 1e9;
for(int i = 1; i <= n; ++i) {
int l, r;
scanf("%d%d", &l, &r);
L = max(L, l);
R = min(R, r);
}
int ans = 0;
if(L > R)
ans = L - R;
printf("%d\n", ans);
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t = 1;
scanf("%d", &t);
for(int ti = 1; ti <= t; ++ti) {
//printf("Case #%d: ", ti);
test_case();
}
}
B - Box
题意:给一个前缀最大值序列,求满足这个序列的任意一个排列。
题解:生成的是满足这个序列的字典序最大的一个排列。而实际上因为这个序列是递增的,怎么乱构造都可以。首先每次最大值变化的位置肯定放这个罪魁祸首,然后扫一遍之后可能会有一些位置没有放,这个时候lower_bound其最大值得到大于等于其最大值的任意一个值。然后--之后就是小于它的最大的一个值,把它放过去就可以了。或者每次就放begin迭代器,直到begin迭代器超过最大值就-1,这样是字典序最小。
注:事实上并没有用到set的性质,所以应该是可以使用链表(但是链表不能用lower_bound然后--,或者说维护起来巨恶心)、并查集(并查集实现的伪单向链表)来实现的。并查集实现的双向链表比真正的双向链表好多了,不容易翻车。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int q[100005];
int p[100005];
set<int> tmp;
void test_case() {
int n;
scanf("%d", &n);
tmp.clear();
for(int i = 1; i <= n; ++i) {
p[i] = -1;
tmp.insert(i);
}
for(int i = 1; i <= n; ++i)
scanf("%d", &q[i]);
p[1] = q[1];
tmp.erase(q[1]);
for(int i = 2; i <= n; ++i) {
if(q[i] > q[i - 1]) {
p[i] = q[i];
tmp.erase(q[i]);
}
}
for(int i = 1; i <= n; ++i) {
if(p[i] == -1) {
auto t = tmp.lower_bound(q[i]);
if(t == tmp.begin()) {
puts("-1");
return;
}
--t;
p[i] = *t;
tmp.erase(t);
}
}
for(int i = 1; i <= n; ++i)
printf("%d%c", p[i], " \n"[i == n]);
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t = 1;
scanf("%d", &t);
for(int ti = 1; ti <= t; ++ti) {
//printf("Case #%d: ", ti);
test_case();
}
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int p[100005];
int q[100005];
struct PseudoPrevLinkedList {
int n;
bool vis[100005];
int prev[100005];
void Init(int _n) {
n = _n;
for(int i = 1; i <= n; ++i) {
vis[i] = 0;
prev[i] = i - 1;
}
}
int FindPrev(int x) {
int r = prev[x];
while(vis[r])
r = prev[r];
int t;
while(prev[x] != r) {
t = prev[x];
prev[x] = r;
x = t;
}
return r;
}
void Remove(int x) {
vis[x] = 1;
}
} fl;
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
p[i] = -1;
scanf("%d", &q[i]);
}
fl.Init(n);
p[1] = q[1];
fl.Remove(p[1]);
for(int i = 2; i <= n; ++i) {
if(q[i] > q[i - 1]) {
p[i] = q[i];
fl.Remove(p[i]);
}
}
for(int i = 1; i <= n; ++i) {
if(p[i] == -1) {
int res = fl.FindPrev(q[i]);
if(!res) {
puts("-1");
return;
}
p[i] = res;
fl.Remove(p[i]);
}
}
for(int i = 1; i <= n; ++i)
printf("%d%c", p[i], " \n"[i == n]);
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t = 1;
scanf("%d", &t);
for(int ti = 1; ti <= t; ++ti) {
//printf("Case #%d: ", ti);
test_case();
}
}
C - Messy
题意:给一个"("")"括号相等的长度不超过2000的括号串,要求每次反转一个子区间,最后使得括号串合法,且经过零点恰好k次。
题解:贪心,每次贪心把前两个字符变成"("")",这样最后就有n/2次零点,然后把开头开始的一小段破坏掉就可以了(假如需要的话,不要画蛇添足),至多用n/2+1次。因为网络的原因没有交上去,问题不大,实力到了自然会收敛。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, k;
string s;
vector<pair<int, int> > ans;
void test_case() {
ans.clear();
cin >> n >> k >> s;
int curp = 1;
while(s.length()) {
if(s[0] == '(' && s[1] == ')') {
s = s.substr(2, s.length());
curp += 2;
} else if(s[0] == ')' && s[1] == '(') {
ans.push_back({curp + 0, curp + 1});
s = s.substr(2, s.length());
curp += 2;
} else if(s[0] == ')') {
int j = 2;
while(s[j] == s[0])
++j;
ans.push_back({curp + 0, curp + j});
string s1 = s.substr(0, j + 1);
string s2 = s.substr(j + 1, s.length());
reverse(s1.begin(), s1.end());
s = s1 + s2;
s = s.substr(2, s.length());
curp += 2;
} else {
int j = 2;
while(s[j] == s[0])
++j;
ans.push_back({curp + 1, curp + j});
string s0 = s.substr(0, 1);
string s1 = s.substr(1, j);
string s2 = s.substr(j + 1, s.length());
reverse(s1.begin(), s1.end());
s = s0 + s1 + s2;
s = s.substr(2, s.length());
curp += 2;
}
}
int tk = n / 2 - k;
if(1 + 2 * tk >= 2)
ans.push_back({2, 1 + 2 * tk});
cout << ans.size() << endl;
for(int i = 0; i < ans.size(); ++i)
cout << ans[i].first << " " << ans[i].second << endl;
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t = 1;
scanf("%d", &t);
for(int ti = 1; ti <= t; ++ti) {
//printf("Case #%d: ", ti);
test_case();
}
}
注:要注意string的substr方法的使用,真的烦死人,substr(a,b)是从a位置开始截取最多b个字符形成的子串。其实假如用静态字符数组会不会方便一些?要是自己写一个String类就可以使用自己的全新方法了。
D1 - Optimal Subsequences (Easy Version)
范围小到100,随便搞,用可持久化Treap/主席树就很简单了,直接在线回答,可惜空间消耗巨大。
D2 - Optimal Subsequences (Hard Version)
题意:给一个n个元素(<=2e5)序列,定义一个长度为k的最优子序列,当其和最大,且满足和最大的条件下字典序最小。每次询问一个k,pos,询问第k个子序列的第pos位置是哪个数。
题解:显然,和最大就是贪心,然后字典序最小的意思就是同大的优先贪前面的,所以按照题目意思排序。离线所有询问然后排序,然后每次往平衡树里面插新的元素直到变成k长的序列,然后找出每个pos位置是谁。然后就觉得很逗了,这个不是直接线段树就完了?具体来说就是线段树上二分,线段树上保存siz,然后利用siz的信息来找当前树上的第pos大,这个操作应该是去主席树那里抄一个就好了。
注:使用可持久化TreapRE了,然后改了一下上限之后WA了,所以还是对可持久化Treap的板子不熟,对空间消耗没有一个合理认识(假如你开512MB会这样?)。然后发现离线回答之后每次回答最短的k,然后就可以破坏掉了,不需要可持久化。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct FHQTreap {
#define ls(p) ch[p][0]
#define rs(p) ch[p][1]
static const int MAXN = 200000 + 5;
int val1[MAXN], val2[MAXN], ch[MAXN][2], rnd[MAXN], siz[MAXN], tot, root;
void Init() {
tot = root = 0;
}
void PushUp(int p) {
siz[p] = siz[ls(p)] + siz[rs(p)] + 1;
}
void SplitValue(int p, int v, int &x, int &y) {
if(!p) {
x = y = 0;
return;
}
if(v < val1[p]) {
y = p;
SplitValue(ls(p), v, x, ls(p));
PushUp(y);
} else {
x = p;
SplitValue(rs(p), v, rs(p), y);
PushUp(x);
}
}
void SplitRank(int p, int rk, int &x, int &y) {
if(!p) {
x = y = 0;
return;
}
if(rk <= siz[ls(p)]) {
y = p;
SplitRank(ls(p), rk, x, ls(p));
PushUp(y);
} else {
x = p;
SplitRank(rs(p), rk - siz[ls(p)] - 1, rs(p), y);
PushUp(x);
}
}
int Merge(int x, int y) {
if(!x || !y)
return x | y;
if(rnd[x] < rnd[y]) {
rs(x) = Merge(rs(x), y);
PushUp(x);
return x;
} else {
ls(y) = Merge(x, ls(y));
PushUp(y);
return y;
}
}
int NewNode(int v, int v2) {
++tot;
ch[tot][0] = ch[tot][1] = 0;
val1[tot] = v, val2[tot] = v2, rnd[tot] = rand();
siz[tot] = 1;
return tot;
}
void Insert(int &root, int v, int v2) {
int x = 0, y = 0;
SplitValue(root, v, x, y);
root = Merge(Merge(x, NewNode(v, v2)), y);
}
void Remove(int &root, int v) {
int x = 0, y = 0, z = 0;
SplitValue(root, v, x, z);
SplitValue(x, v - 1, x, y);
y = Merge(ls(y), rs(y));
root = Merge(Merge(x, y), z);
}
int GetRank(int &root, int v) {
int x = 0, y = 0;
SplitValue(root, v - 1, x, y);
int rk = siz[x] + 1;
root = Merge(x, y);
return rk;
}
int GetValue2(int &root, int rk) {
int x = 0, y = 0, z = 0;
SplitRank(root, rk, x, z);
SplitRank(x, rk - 1, x, y);
int v2 = val2[y];
root = Merge(Merge(x, y), z);
return v2;
}
#undef ls(p)
#undef rs(p)
} ft;
struct Node {
int val;
int pos;
Node(int v = 0, int p = 0) {
val = v;
pos = p;
}
bool operator<(const Node &node)const {
if(val != node.val)
return val > node.val;
return pos < node.pos;
}
} node[200005];
struct Query {
int id, k, pos, ans;
} q[200005];
bool cmp1(const Query &q1, const Query &q2) {
if(q1.k != q2.k)
return q1.k < q2.k;
return q1.pos < q2.pos;
}
bool cmp2(const Query &q1, const Query &q2) {
return q1.id < q2.id;
}
void InputQuery(int n) {
for(int i = 1; i <= n; ++i) {
scanf("%d%d", &q[i].k, &q[i].pos);
q[i].id = i;
}
sort(q + 1, q + 1 + n, cmp1);
}
void OutputQuery(int n) {
sort(q + 1, q + 1 + n, cmp2);
// for(int i = 1; i <= n; ++i)
// printf("%d%c", q[i].ans, " \n"[i == n]);
for(int i = 1; i <= n; ++i)
printf("%d\n", q[i].ans);
}
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &node[i].val);
node[i].pos = i;
}
sort(node + 1, node + 1 + n);
int m;
scanf("%d", &m);
InputQuery(m);
ft.Init();
int j = 1;
for(int i = 1; i <= m; ++i) {
while(ft.siz[ft.root] < q[i].k) {
ft.Insert(ft.root, node[j].pos, node[j].val);
++j;
}
q[i].ans = ft.GetValue2(ft.root, q[i].pos);
}
OutputQuery(m);
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t = 1;
//scanf("%d", &t);
for(int ti = 1; ti <= t; ++ti) {
//printf("Case #%d: ", ti);
test_case();
}
}
E - 暂缺
F1 - Wrong Answer on test 233 (Easy Version)
题意:一套选择题,有n道题,每道题k个选项,选对得1分,选错得0分。求有多少种答案,使得其循环右移一次之后得分比之前严格高,模998244353。
题解:看起来很像dp,但是怎么搞?