Codeforces Round #602
这场比赛本来是抓着当暑假集训的个人赛的,但是打得还不错,就想写篇题解。
Math Problem
开一个mx记录左端点的最大值,开一个mn记录右端点的最小值,答案是max(mx - mn, 0)。
#include <iostream>
using namespace std;
#define INF 0x7fffffff
int main()
{
int _;
cin >> _;
while(_--)
{
int n;
cin >> n;
int mx = -INF, mn = INF;
for(int i = 1; i <= n; i++)
{
int l, r;
cin >> l >> r;
mx = max(mx, l);
mn = min(mn, r);
}
cout << max(mx - mn, 0) << endl;
}
return 0;
}
Box
题目中给出的q数组中每个数第一次出现的位置就是那个数在p数组中出现的位置,然后将没有出现过的数按照由小到大的顺序插入到p数组中空缺的位置,之后再对p数组跑一遍前缀最大值,如果跑出来的结果跟题目中给出的q数组有矛盾的话输出-1,否则输出构造好的p数组。
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
#define N 100005
int q[N], p[N], t[N], flag[N];
vector <int> v;
int main()
{
int _;
cin >> _;
while(_--)
{
v.clear();
memset(q, 0, sizeof(q));
memset(p, 0, sizeof(p));
memset(t, 0, sizeof(t));
memset(flag, 0, sizeof(flag));
int n;
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> q[i];
if(!flag[q[i]])
{
flag[q[i]] = 1;
p[i] = q[i];
}
}
for(int i = 1; i <= n; i++)
if(!flag[i])
v.push_back(i);
int cnt = 0;
for(int i = 1; i <= n; i++)
if(!p[i])
p[i] = v[cnt++];
for(int i = 1; i <= n; i++)
t[i] = max(t[i - 1], p[i]);
int f = 1;
for(int i = 1; i <= n; i++)
{
if(q[i] != t[i])
{
f = 0;
break;
}
}
if(f)
{
for(int i = 1; i <= n; i++)
{
if(i == 1)
cout << p[i];
else
cout << " " << p[i];
}
cout << endl;
}
else
cout << -1 << endl;
}
return 0;
}
Messy
考虑先将所有的左括号移到左边,右括号移到右边,可以这样操作:遍历1 ~ n,如果当前字符是右括号,就一直向右找到它右边的第一个左括号,然后翻转这段区间,这样最多经过n / 2步就能达到我们的目的。之后的任务就是根据题目的要求构造出k个前缀,怎么构造呢?会发现在所有的左括号都在左边,右括号都在右边的情况下,只需要将2 * i到n / 2 + i这段区间翻转就能多出来一个前缀,所以只需要这样翻转就能达到题目的要求。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
#define N 2005
char s[N];
vector <pair <int, int> > ans;
int main()
{
int _;
cin >> _;
while(_--)
{
ans.clear();
int n, k;
cin >> n >> k;
cin >> s + 1;
for(int i = 1; i <= n; i++)
{
if(s[i] == ')')
{
int st = i, ed = i + 1;
while(ed <= n && s[ed] == ')')
ed++;
if(ed == n + 1)
break;
ans.push_back({st, ed});
reverse(s + st, s + ed + 1);
}
}
for(int i = 1; i < k; i++)
{
ans.push_back({2 * i, n / 2 + i});
reverse(s + 2 * i, s + n / 2 + i + 1);
}
cout << ans.size() << endl;
for(int i = 0; i < ans.size(); i++)
cout << ans[i].first << " " << ans[i].second << endl;
}
return 0;
}
** Optimal Subsequences (Easy Version)**
下一道题的简单版本,数据很水(应该叫小),直接暴力就能过。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 105
int a[N], vis[N];
struct Node
{
int a;
int pos;
}node[N];
bool cmp(Node x, Node y)
{
if(x.a != y.a)
return x.a > y.a;
else
return x.pos < y.pos;
}
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
node[i].a = a[i];
node[i].pos = i;
}
sort(node + 1, node + n + 1, cmp);
int m;
cin >> m;
while(m--)
{
memset(vis, 0, sizeof(vis));
int k, pos;
cin >> k >> pos;
for(int i = 1; i <= k; i++)
vis[node[i].pos] = 1;
int cnt = 0;
for(int i = 1; i <= n; i++)
{
if(vis[i])
cnt++;
if(cnt == pos)
{
cout << a[i] << endl;
break;
}
}
}
return 0;
}
Optimal Subsequences (Hard Version)
数据放大到2e5之后就不能再暴力了,来想正常的解法,顺着上一题的思路,根据题目的要求,我们先选的数是较大的,在数一样大的情况下选的是出现较靠前的,每次都问我们选出来的序列中下标是pos的数是多少,可以这样考虑:建立一颗线段树,线段树维护原数组某个区间内被选出的数的个数,那么当我们选完数之后,问我们选出来的序列中下标是pos的数是多少不就可以转换成线段树求区间第k小了吗?这个问题解决了,但是可能会有很多次在线段树上插入点删除点的操作,m次询问,最多有mn次插入点删除点的操作,复杂度mnlogn,直接暴上天,怎么解决这个问题?很简单,离线处理。将所有的询问都记录下来,如果按照k由小到大排序,那么就只用写ins函数就行;如果按照k由大到小排序,那么就只用写del函数就行。在这里我是按照k由小到大排序写的,之后就是在线段树上插点查询了,复杂度O(nlogn + mlogm + mlogn)。
#include <iostream>
#include <algorithm>
using namespace std;
#define N 200005
int a[N], ans[N];
struct Number
{
int a;
int pos;
}number[N];
struct Q
{
int k;
int pos;
int id;
}q[N];
struct Node
{
int l;
int r;
int val;
int sum;
}node[N << 2];
bool cmp1(Number x, Number y)
{
if(x.a != y.a)
return x.a > y.a;
else
return x.pos < y.pos;
}
bool cmp2(Q x, Q y)
{
if(x.k != y.k)
return x.k < y.k;
else if(x.pos != y.pos)
return x.pos < y.pos;
else
return x.id < y.id;
}
void push_up(int rt)
{
node[rt].sum = node[rt << 1].sum + node[rt << 1 | 1].sum;
}
void build(int rt, int l, int r)
{
if(l == r)
node[rt] = {l, r, a[l], 0};
else
{
node[rt] = {l, r, 0, 0};
int m = l + r >> 1;
build(rt << 1, l, m);
build(rt << 1 | 1, m + 1, r);
}
}
void ins(int rt, int pos)
{
if(node[rt].l == pos && node[rt].r == pos)
node[rt].sum = 1;
else
{
int m = node[rt].l + node[rt].r >> 1;
if(pos <= m)
ins(rt << 1, pos);
else
ins(rt << 1 | 1, pos);
push_up(rt);
}
}
int query(int rt, int pos)
{
if(node[rt].l == node[rt].r)
return node[rt].val;
else
{
if(node[rt << 1].sum >= pos)
return query(rt << 1, pos);
else
return query(rt << 1 | 1, pos - node[rt << 1].sum);
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
number[i].a = a[i];
number[i].pos = i;
}
sort(number + 1, number + n + 1, cmp1);
int m;
cin >> m;
for(int i = 1; i <= m; i++)
{
cin >> q[i].k >> q[i].pos;
q[i].id = i;
}
sort(q + 1, q + m + 1, cmp2);
build(1, 1, n);
int cnt = 0;
for(int i = 1; i <= m; i++)
{
while(cnt < q[i].k)
ins(1, number[++cnt].pos);
ans[q[i].id] = query(1, q[i].pos);
}
for(int i = 1; i <= m; i++)
cout << ans[i] << endl;
return 0;
}