Codeforces Round #649 (Div. 2)
Posted last--whisper
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Codeforces Round #649 (Div. 2)相关的知识,希望对你有一定的参考价值。
Codeforces Round #649 (Div. 2) -- WKL
(mathcal{A})题: (mathrm{XXXXX})
Greedy
implementation
*1200
第一题,要求的是求一段子数组的区间和,要求该区间和不被(x)整除且长度尽可能长。
显然,对于这类题目可以想到以下几点:
- (MOD)的使用
- 贪心与构造
思路如下:定义数组为(arr)。我们首先看(sum arr)是否符合要求,假如符合显然这个是最长的。假如不符合呢?我们只需要从两端找到第一个(mathrm{mod} x ot= 0)的即可,然后从中选择更优的就解决了。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int f[N];
// 贪心,要最长, 左右减去就好了(T1一般不是贪心就是数学问题)
int main(){
int t; cin >> t;
while (t--){
int n, x;
cin >> n >> x;
int sum(0), l(-1), r(-1), ans(-1);
for (int i = 0; i < n; ++ i){
cin >> f[i];
sum += f[i];
if (f[i] % x && l == -1) l = i;
if (f[i] % x) r = i;
}
if (sum % x) cout << n << endl;
else if (l == -1 && r == -1) cout << -1 << endl;
else{
ans = max(n - l - 1, r);
cout << ans << endl;
}
}
return 0;
}
(mathcal{B})题: (mathrm{Most; socially-distanced; subsequence})
Greedy
two pointers
*1300
(mathcal{B}) 题的话从题干中获取如下两点就好了:
-
[egin{array} x seq = {s_1, s_2, cdots, s_k}\\mathrm{get}-max{left|s_1 - s_2 ight| + left|s_2 - s_3 ight| + cdots + left|s_{k-1} - s_{k} ight|} \\mathrm{with-}min{len(seq)} end{array} ]
根据分析,当序列({s_i, s_j, s_k})单调时,(|s_i - s_j| + |s_j - s_k| = |s_i - s_k|) 。因此我们只需要在添加首尾两点之后找到极大极小值点就好了。就像化学里面能量计算一样
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int f[N];
// 找转折点 因为单调的话是没有用的,只有转折才有用(有点像化学里面的能量变化问题)
int main(){
int t; cin >> t;
while (t--){
vector<int> ans;
int n; cin >> n;
for (int i = 0; i < n; ++ i) cin >> f[i];
for (int i = 0; i < n; ++ i){
if (i == 0 || i == n -1 || ((f[i] < f[i - 1] && f[i] < f[i + 1]) || (f[i] > f[i - 1] && f[i] > f[i + 1])))
ans.push_back(f[i]);
}
cout << ans.size() << endl;
for (auto &x: ans) cout << x << " "; cout << endl;
}
}
(mathcal{C})题: (mathrm{Ehab; and; Prefix; MEXs})
Greedy
constructive algorithms
*1600
需要注意的是
It‘s guaranteed that (a_ileq a_{i+1}) for (1leq ileq n).
一共有更具MEX的定义可以获得两点信息:
- (b_i)首先应该填入(1 sim a_i - 1)的数字,假如已经填满了,则填允许填的数字
- 实际上,作为升序序列,从(0)到(max{a_i}),除了(a_i)自身外,其他的数字都应该出现。因此可以贪心去做,也可以维护一个链表去做。
首先是比赛中的方法,通过维护一个链表(链表中是还没有被添加的数字),每次要加入时判断还有不要小于(a_i)的数字未被添加:
- 假如只有一个,或没有则正常添加。
- 假如有多个,则说明无法成立。
#include<bits/stdc++.h>
using namespace std;
#define MP(x, y) make_pair(x, y)
#define fi first
#define se second
using PII = pair<int ,int>;
const int N = 1e5 + 50;
int c[N], arr[N]; // c[i] 代表i存在的个数,当大于0时说明不允许添加
int main(){
int n; cin >> n;
memset(c, 0, sizeof(c));
for (int i = 0; i < n; ++ i) cin >> arr[i], ++ c[arr[i]];
int pt(0), scan(0);
vector<int> ans;
vector<PII> que; // 模拟的链表, pt是指向头的指针,scan是用来添加那些已经填充满了的b_i
que.clear();
for (int i = 0; i <= n; ++ i) que.push_back(MP(i, 0)); // 初始化
for (int i = 0; i < n; ++ i){
int lim = arr[i] - 1;
if (que[pt].fi > lim){ // 假如当前链表头部的值大于lim,说明该加入的已经假如
while (c[scan] != 0 || que[scan].se == 1) ++ scan; // 往后面找一个值填进去
que[scan].se = 1;
ans.push_back(que[scan].fi);
while (que[pt].se == 1) ++ pt; // 更新point
-- c[arr[i]];
}else {
ans.push_back(que[pt].fi); // 说明不满足条件1,首先加一个进去再判断是否满足条件
que[pt].se = 1;
while (que[pt].se == 1) ++ pt;
-- c[arr[i]];
if (que[pt].fi > lim) continue;
else {
cout << -1 << endl;
return 0;
}
}
}
for (auto &x: ans) cout << x << " "; cout << endl;
return 0;
}
还有一种,便是题解所给的办法。通过贪心和构造快速解决。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 50;
int c[N], a[N], b[N]; // c[i] 代表i存在的个数,当大于0时说明不允许添加
int main(){
int n; cin >> n;
memset(b, -1, sizeof(b));
memset(c, 0, sizeof(c));
for (int i = 0; i < n ;++ i){
cin >> a[i];
if (i != 0 && a[i] != a[i - 1]){
b[i] = a[i - 1];
++ c[b[i]];
}
}
++ c[a[n - 1]];
int m = 0;
for (int i = 0; i < n; ++ i){
if (b[i] == -1){
while (c[m]) ++ m;
b[i] = m;
++ c[b[i]];
}
cout << b[i] << " ";
}
cout << endl;
return 0;
}
(mathcal{D})题: (mathrm{Ehab‘s; Last; Corollary})
dfs
graphs
trees
*2100
extra: 最小环
这题,看了半天才会写。这题要求的是对于给出一张 (n) 个点的无向连通图和一个常数 (k)。你需要解决以下任何一个问题中的一个:
- 找出一个大小为(lceil frac{k}{2} ceil)的独立集。
- 找出一个大小不超过(k)的环。
独立集: 独立集是一个点的集合,满足其中任意两点之间在原图上没有边直接相连。
首先你得理解出题人的这句话, 才能方便的解决这个问题:
I have a proof that for any input you can always solve at least one of these problems, but it‘s left as an exercise for the reader.
就是,为什么一定能有一个解决办法呢?
我们从两种情况来看:
情况1: 假如所给的无向图根本没有环, 可以直接用黑白染色解决(mathcal{Q_1}),即将更大的染色集输出即可。
情况2:假如存在环,也有两种情况:
-
- 环的长度(L)小于等于(k), 那好办直接输出就好了!
- 假如环的大小超过(k): 对于一个最小环,对于环上点(i,j)(假设这里边的权值都为(1))。设(dist_{ij})为顺着环走的最短路径;(dist^prime_{ij})为(i ightarrow j)的最短路径。显然不存在(dist^prime_{ij} < dist_{ij}),否则我们可以直接走(dist^prime)所对应的路径,可以构成一个更小的环。 换一句话说,设(mathrm{node})加入环的时间为(t_i),则两个可以直接连接的结点之间的加入环的时间差:(Delta t = 1) 因此对于一个长度大于(k)的最小环,我们每隔一点进行输出,必定是一个大小大于(lceil frac{k}{2} ceil)的独立集。
如上图所示,假如出现这种情况,直接取出({2,3,4})即可。
而我们如何确认这种割裂情况呢?我们先用(mathrm{dfs})任意找到一个圆环,对于每个边(mathrm{edge_i}),设(u,v)为边的端点。假如(u,v)都在环中,且他们之间的距离不为(1),则说明这个环不是最小环,可以进一步规约。
总的思路如下:
①: 首先利用(mathrm{dfs})进行遍历一边黑白染色,一边记录每个结点(mathrm{node})出现的时间(t_i),看是否存在环即(t_i)已经进行赋值
②: 若找到环,便将(t_i ightarrow t_{cur})时间内所有加入的结点连接在一起,组成初始环(用deque存)
③: 若(mathrm{dfs})之后没有找到环,利用黑白染色输出结点,结束。
④: 若找到环,对先确认是否存在割裂情况,没有则转⑤,假如存在则将多余的结点从双端队列中弹出。
⑤: 判断大小,若小于(k)则直接输出,若大于(k)则间隔输出,结束。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 50;
vector<int> p, E[MAXN], col[2];
deque<int> cicrle;
int u[MAXN], v[MAXN], pos[MAXN];
bool in_cicrle[MAXN];
void dfs(int x, int fa= 0){
pos[x] = p.size();
p.push_back(x);
col[p.size() % 2].push_back(x); // 黑白染色
for (auto &go: E[x]){
if (go == fa) continue;
if (pos[go] == -1) dfs(go, x); // 是否访问过,考虑pos[i] == 0的情况
else {
if (cicrle.empty()){
for (int i = pos[go]; i <= pos[x]; ++ i){ // 将时间t_go -> t_x内假如的结点都加进去
cicrle.push_back(p[i]);
in_cicrle[p[i]] = true;
}
}
}
}
p.pop_back();
}
int main(){
int n, m, k;
cin >> n >> m >> k;
memset(pos, -1, sizeof(pos));
p.clear();
cicrle.clear();
for (int i = 0; i <= n; ++ i) E[i].clear();
for (int i = 0; i < m; ++ i){
cin >> u[i] >> v[i];
E[u[i]].push_back(v[i]);
E[v[i]].push_back(u[i]);
}
dfs(1);
if (cicrle.empty()){ // 情况③
cout << "1" << endl;
if (col[0].size() < col[1].size()) swap(col[0], col[1]);
for (int i = 0; i < ((k + 1) / 2); ++ i) cout << col[0][i] << " ";
cout << endl;
return 0;
}
for (int i = 0; i < m; ++ i){ // 情况④, 进行规约
if (in_cicrle[v[i]] && in_cicrle[u[i]] && abs(pos[v[i]] - pos[u[i]]) != 1){
while (cicrle.front() != v[i] && cicrle.front() != u[i]){
in_cicrle[cicrle.front()] = false;
cicrle.pop_front();
}
while (cicrle.back() != v[i] && cicrle.back() != u[i]){
in_cicrle[cicrle.back()] = false;
cicrle.pop_back();
}
}
}
if (cicrle.size() <= k){ // 情况⑤
cout << "2" << endl;
cout << cicrle.size() << endl;
for (int i = 0; i < cicrle.size(); ++ i) cout << cicrle[i] << " "; cout << endl;
}else {
cout << "1" << endl;
for (int i = 0; i < ((k + 1) / 2); ++ i){
cout << cicrle[2 * i] << " ";
}
cout << endl;
}
}
其实这题,最开始想直接求最小环,然后学习了(mathcal{Floyd - Warshall})算法求最小环,但是因为点太多了,时间复杂度肯定超过就没有用了。但是也碰到了一个坑:
mini_cicrle = min(mini_ciclre, edge[i][k] + edge[k][j] + dist[i][j])
,会出现三个INF
相加,所以假如INF = 0x3f3f3f3f
的话会直接溢出,需要注意!!!
(mathcal{E})题: (mathrm{X-OR})
bitmasks
interactive
*2700
这题是交互题,考察异或,(otimes)的考点无非就是:
- (x otimes0 = x)
- (x otimes x = 0)
有一个固定的长度为 (n) 的排列 (P),其值域为 ([0,n-1]),你可以进行不超过 (4269) 次询问,之后你需要输出这个排列 (P)。
利用性质一就好了,所以现在我们需要做到就是找到(0)的位置。
- 因为(y otimes x leq x),所以不存在其他的数异或x比0异或x要小,所以假如存在(a otimes x < b otimes x),则(b)不可能为(0) -- 结论(1)
- 因为(x otimes0 = x),则如果(a ot = b),则必然有(a otimes 0 ot = b otimes 0),也就是说,如果存在一个数(c),使得(a ot = b, aotimes c = botimes c),则(c)不可能为(0) -- 结论(2)
所以,我们可以先打乱顺序后任取两个(fi),(se)。然后顺去取后面的(th), (val = mathrm{Query(fi,se)} quad temp = mathrm{Query(fi,th)}):
- 对于(val > temp)根据结论(1),(se)所在位置不可能为(0),所以(th ightarrow se, temp ightarrow val)
- 对于(val < temp)根据结论(1),不用更新
- 对于(val == temp)根据结论(2),(fi)所在位置不可能为(0),所以(th ightarrow fi, mathrm{Query(se,th)} ightarrow val)
#include<bits/stdc++.h>
using namespace std;
const int MAXN = (1 << 11) + 50;
int n, p[MAXN];
vector<int> ans;
inline int query(int x, int y){
cout << "? " << x << ‘ ‘ << y << endl;
cout.flush();
int ret; cin >> ret;
return ret;
}
int main(){
ans.clear();
srand(20010410);
cin >> n;
for (int i = 0; i < n; ++ i) p[i] = i + 1;
random_shuffle(p, p + n);
int fi = p[0], se = p[1], val = query(fi, se);
for (int i = 2; i < n; ++ i){
int temp = query(se, p[i]);
if (temp > val) continue;
else if (temp < val){ // fi xor se > se xor th --> fi != zero
fi = p[i];
val = temp;
}else {
se = p[i];
val = query(fi, p[i]);
}
}
int zero_idx(0);
while (true){
int i = rand() % n + 1;
if (fi == i || se == i) continue;
int v1 = query(fi, i), v2 = query(se, i);
if (v1 == v2) continue;
zero_idx = v1 < v2 ? fi : se;
break;
}
for (int i = 1; i <= n; ++ i){
if (i != zero_idx) ans.push_back(query(i,zero_idx));
else ans.push_back(0);
}
cout << "! ";
for (auto &x: ans) cout << x << " ";
cout << endl;
cout.flush();
return 0;
}
(mathrm{Think; twice,; Code; once})
以上是关于Codeforces Round #649 (Div. 2)的主要内容,如果未能解决你的问题,请参考以下文章
Codeforces Round #649 (Div. 2)ABC
Codeforces Round #649 (Div. 2) C. Ehab and Prefix MEXs
Codeforces Round #649 (Div. 2) B. Most socially-distanced subsequence
Codeforces Round #649 (Div. 2) D - Ehab's Last Corollary dfs