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

Codeforces Round #436 E. Fire(背包dp+输出路径)

[ACM]Codeforces Round #534 (Div. 2)