Loading

Educational Codeforces Round 132 (Rated for Div. 2) A - E

传送门

最近没进入状态,好久没更新博客了,有点开摆的感觉,不过集训期间肯定会认认真真打的

多校的题目是真的难顶

A - Three Doors

模拟一下

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>
#include <functional>
#include <map>
#include <set>
#include <cmath>
#include <cstring>
#include <deque>
#include <stack>
using namespace std;
typedef long long ll;
#define pii pair<int, int>
const ll maxn = 2e5 + 10;
const ll inf = 1e17 + 10;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while(t--)
    {
        int x;
        cin >> x;
        int a[4];
        for(int i=1; i<=3; i++) cin >> a[i];
        int ans = 0;
        while(x)
        {
            ans++;
            x = a[x];
        }
        if(ans == 3) cout << "yes\n";
        else cout << "no\n";
    }
    // cout << endl;

    return 0;
}

B - Also Try Minecraft

“差分”的前缀和还有后缀和

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>
#include <functional>
#include <map>
#include <set>
#include <cmath>
#include <cstring>
#include <deque>
#include <stack>
using namespace std;
typedef long long ll;
#define pii pair<int, int>
const ll maxn = 2e5 + 10;
const ll inf = 1e17 + 10;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<ll>a(n + 10, 0), dif1(n + 10, 0), dif2(n + 10, 0);
    for(int i=1; i<=n; i++)
        cin >> a[i];
    for(int i=2; i<=n; i++)
        dif1[i] = max(0ll, -a[i] + a[i-1]) + dif1[i - 1];
    for(int i=n-1; i>=1; i--)
        dif2[i] = max(0ll, a[i+1] - a[i]) + dif2[i + 1];
    while(m--)
    {
        int l, r;
        cin >> l >> r;
        if(l < r)
            cout << dif1[r] - dif1[l] << "\n";
        else
            cout << dif2[r] - dif2[l] << "\n";
    }
    // cout << endl;

    return 0;
}

C - Recover an RBS

首先有一种绝对能够构造的方式,从左到右能放左括号就放,放完就放右括号

再考虑能不能找到一对自己填下去的括号(左括号和右括号)交换,交换后合法

在栈模拟括号匹配的时候,当前栈存在的括号数量,我称之为括号的级数:

1 2 2 2 3 3 2 1

如果想要交换的话,相当于右括号往前挪,左括号往后挪,因此这两个符号中间的所有符号级数都下降 \(2\),而两个括号本身的位置级数下降 \(1\),在上述定义的级数来说,最小为 \(1\),因此就必须让选中的两个括号之间的级数至少为 \(3\),且本身至少为 \(2\)

1 2 2 1 1 1 1 1

上述交换了第 \(4\) 个括号和第 \(7\) 个括号后的级数变化

这样想的话就只用考虑最右边的左括号和最左边的右括号是否可以交换(因为其他的交换方式都必定要改变他们中间的括号的级数)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>
#include <functional>
#include <map>
#include <set>
#include <cmath>
#include <cstring>
#include <deque>
#include <stack>
using namespace std;
typedef long long ll;
#define pii pair<int, int>
const ll maxn = 2e5 + 10;
const ll inf = 1e17 + 10;
int vis[maxn], alp[maxn];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while(t--)
    {
        string s;
        cin >> s;
        int a = 0, b = 0;
        for(int i=0; i<s.length(); i++)
        {
            if(s[i] == '(') a++;
            else if(s[i] == ')') b++;
        }
        if(a * 2 == s.length() || b * 2 == s.length() || a + b == s.length()) {cout << "YES\n"; continue;}
        int l = -1, r = -1, now = 0;
        for(int i=0; i<s.length(); i++)
        {
            vis[i] = s[i] == '?';
            if(s[i] == '?' && a * 2 != s.length()) {s[i] = '('; a++;}
            else if(s[i] == '?') s[i] = ')';
            if(s[i] == '(') alp[i] = ++now;
            else alp[i] = now--;
            if(vis[i] == 0) continue;
            if(s[i] == '(') l = i;
            else if(r == -1) r = i;
        }
        int f = 1;
        for(int i=l+1; i<r && f; i++)
            if(alp[i] <= 2) f = 0;
        if(alp[l] == 1 || alp[r] == 1) f = 0;
        if(f) cout << "NO\n";
        else cout << "YES\n";
    }
    cout << endl;

    return 0;
}

D - Rorororobot

线段树 或 树状数组

题目意思就不转述了,其实感觉懂了题目就会做了

如果 \(x\) 坐标的差和 \(y\) 坐标的差都是 \(k\) 的倍数,说明如果没有遮挡物的话,就必然能到

考虑遮挡物,由于不要求最短路,可以直接走到最深处,看能不能过去就好了

判断最深处是否能过,就找这些列的区间最大值是否小于能走到的最深处

如果 wa4,说明要检查一下线段树查询的调用,题目未保证起点与终点的列有大小关系

我这里用的线段树维护区间最大值

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>
#include <functional>
#include <map>
#include <set>
#include <cmath>
#include <cstring>
#include <deque>
#include <stack>
using namespace std;
typedef long long ll;
#define pii pair<int, int>
const ll maxn = 2e5 + 10;
const ll inf = 1e17 + 10;
int tr[maxn], sum[maxn], n, m;

inline int lowbit(int x)
{
    return  x & (-x);
}

void add(int x, int val)
{
    for(int i=x; i<=m; i+=lowbit(i))
        sum[i] = val > sum[i] ? val : sum[i];
}

ll query(int l, int r)
{
    int ans = 0;
    while(l <= r)
    {
        int nex_l = r - lowbit(r) + 1;
        if(nex_l < l)
            ans = max(tr[r--], ans);
        else
        {
            ans = max(sum[r], ans);
            r = nex_l - 1;
        }
    }
    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    for(int i=1; i<=m; i++)
    {
        cin >> tr[i];
        add(i, tr[i]);
    }
    int q;
    cin >> q;
    while(q--)
    {
        int sx, sy, ex, ey, k;
        cin >> sx >> sy >> ex >> ey >> k;
        if(abs(ex - sx) % k || abs(ey - sy) % k) {cout << "NO\n"; continue;}
        if(sy > ey) swap(sy, ey);
        int maxx = query(sy, ey);
        int r = (n - sx) / k * k + sx;
        if(maxx >= r) {cout << "NO\n"; continue;}
        cout << "YES\n";
    }
    // cout << endl;

    return 0;
}

E - XOR Tree

启发式合并

直接枚举所有点对,显然会超时

考虑对每个结点维护一个 set,代表从该结点到其子树的所有结点的路径异或和

当前结点的 set 可以从其所有的子节点更新过来,即为其所有子节点中 set 里所有值 异或 当前结点值,然后插入当前结点的 set 里。这里用了启发式合并,就是将 set 较小的插入到 set 较大的中去

简单证明其复杂度:这样操作对于一个点来说,如果它被插入到别的 set 一次,说明其所在的 set 大小会增大一倍,因此这个点至多被插入 \(logn\) 次,因此每个结点至多被插入 \(logn\) 次,复杂度为 \(O(nlogn)\)

接下来考虑出现异或值为 \(0\) 的路径的情况

如果子节点的 set 出现了与当前结点的 set 相同的值,说明其异或和为 \(0\);如果没有出现这样的值,就直接启发式合并起来,表示已经合并到当前结点中

如果出现了异或和为 \(0\) 的路径,就直接让当前结点的值变成一个很大的 2 的次幂,这样只要经过该结点就一定不会出现异或和为 \(0\) 的情况,当前此时可以直接清空该结点的 set,因为必然不会对祖先结点造成影响

这里出现一个问题,就是如果维护的是节点到当前结点的路径异或和的话,每次进行合并的时候,都要对子节点里的 set 进行异或,启发式合并就失去了意义

因此我们考虑初始化维护所有结点到根的异或和,子树进行合并的时候,就可以直接异或,因为从当前结点到根的那段路径异或和必然会消为 \(0\),不造成影响

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;
const int maxn = 2e5 + 10;
vector<int>gra[maxn];
set<int>s[maxn];
int ans = 0, a[maxn];
int sum[maxn], id[maxn];

void init(int now, int pre)
{
    sum[now] ^= a[now];
    for(int nex : gra[now])
    {
        if(nex == pre) continue;
        sum[nex] ^= sum[now];
        init(nex, now);
    }
}

void solve(int now, int pre)
{
    id[now] = now;
    s[now].insert(sum[now]);
    int f = 0;
    for(int nex : gra[now])
    {
        if(nex == pre) continue;
        solve(nex, now);
        if(f) continue;
        int u = id[now], v = id[nex];
        if(s[u].size() > s[v].size()) swap(u, v);
        for(int x : s[u])
        {
            if(s[v].count(x ^ a[now]))
            {
                f = 1;
                break;
            }
        }
        for(int x : s[u])
            s[v].insert(x);
        id[now] = v;
        // s[u].clear();
    }
    if(f)
    {
        ans++;
        s[id[now]].clear();
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    for(int i=1; i<=n; i++) cin >> a[i];
    for(int i=1; i<n; i++)
    {
        int x, y;
        cin >> x >> y;
        gra[x].push_back(y);
        gra[y].push_back(x);
    }
    init(1, 1);
    solve(1, 1);
    cout << ans << endl;
    return 0;
}
posted @ 2022-07-23 00:27  dgsvygd  阅读(41)  评论(0编辑  收藏  举报