Codeforces Round 969 Div.2+Div.1

A. Dora's Set

注意到任意两个偶数的 \(\gcd\) 都大于 \(1\) ,因此选择的三个数中至多一个偶数,而注意到相邻的奇数一定互质,因此每次选两个奇数一个偶数,答案=奇数的个数÷2

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}



int main()  
{   
    int T = read();
    while(T--)
    {
        int l = read(), r = read();
        int cnt = 0;
        for(int i = l; i <= r; ++i)
            if(i & 1) ++cnt;
        printf("%d\n", cnt >> 1);
    }
    return 0;   
}

B. Index and Maximum Value

每次对某个值域中的数+1或-1,问最大值?

只记录最大值即可,每次操作判断是否会操作最大值

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}



int main()  
{   
    int T = read();
    while(T--)
    {
        int n = read(), m = read();
        int mx = 0;
        for(int i = 1; i <= n; ++i)
        {
            int a = read();
            mx = max(mx, a);
        }
        while(m--)
        {
            char c;
            scanf(" %c", &c);
            int l = read(), r = read();
            if(c == '+')
            {
                if(l <= mx && mx <= r) ++mx;
            }else
            {
                if(l <= mx && mx <= r) --mx;
            }
            printf("%d ", mx);
        }
        printf("\n");
    }
    return 0;   
}

C. Dora and C++

发现只需要关注相对大小,对 \(x, y\) 反复加 \(a, b\) 总可以使相对大小变化 \(d = \gcd(a, b)\)

使 \(x, y\) 更接近,都放到 \([0, d - 1]\) 中,即所有的数对 \(d\) 取模

发现可以把后缀的一些数放到 \([-d, -1]\)

枚举从哪个点开始断即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

int gcd(int x, int y)
{
    if(!y) return x;
    return gcd(y, x % y);
}

const int N = 1e5 + 5;
int A[N];

int main()  
{   
    int T = read();
    while(T--)
    {
        int n = read(), x = read(), y = read(), d = gcd(x, y);
        // printf("x = %d, y = %d, d = %d\n", x, y, d);
        for(int i = 1; i <= n; ++i) A[i] = read() % d;
        sort(A + 1, A + n + 1);
        // for(int i = 1; i <= n; ++i) printf("%d ", A[i]);
        // printf("\n");
        int ans = A[n] - A[1];
        for(int i = 1; i < n; ++i)
        {
            ans = min(ans, d - (A[i + 1] - A[i]));
        }
        printf("%d\n", ans);
    }
    return 0;   
}

D. Iris and Game on the Tree

发现如果某个叶子的值和根一样,那么这条路径没有贡献,只有当根和叶子值不同时会有贡献

根确定时两个人抢叶子,根不确定时发现第一个填根的人一定不优,此时路径上的其他 \(?\) 决定谁先手

贺的代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 1e5 + 5;
int n;
vector<int> e[N];
int du[N];
char s[N];

int main()  
{   
    int T = read();
    while(T--)
    {
        n = read();
        for(int i = 1; i <= n; ++i) du[i] = 0;
        for(int i = 1; i < n; ++i)
        {
            int u = read(), v = read();
            ++du[u], ++du[v];
        }
        scanf("%s", s + 1);
        int X = 0, Y = 0, Z = 0, K = 0;
        for(int i = 2; i <= n; ++i)
        {
            if(du[i] == 1)
            {
                if(s[i] == '1') ++X;
                if(s[i] == '0') ++Y;
                if(s[i] == '?') ++Z;
            }else if(s[i] == '?') ++K;
        }
        int ans = 0;
        if(s[1] == '?')
        {
            ans = max(X, Y) + Z / 2;
            if(X == Y)
            {
                if(K % 2 == 1) ans = max(ans, X + (Z + 1) / 2);
                else ans = max(ans, X + Z / 2);
            }
        }else
        {
            if(s[1] == '1') ans = Y + (Z + 1) / 2;
            else ans = X + (Z + 1) / 2;
        }
        printf("%d\n", ans);
    }
    return 0;   
}

E. Iris and the Tree

记一个点对路径上已经确定的边的权值为 \(a\) ,所有已经确定的边的权值为 \(b\) ,如果这个点对路径上的边没有全部确定,那么最大值可以取到 \(w - b + a\)

同时注意到一条边只会被两个点对包含,记录每个路径上的边被覆盖的次数即可,同时维护 \(w - b\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 2e5 + 5;
int n;
ll w;
vector<int> e[N];

int dep[N], f[N][20], dfn[N], low[N], rk[N], tot;

void dfs(int k, int fa)
{
    dep[k] = dep[fa] + 1, f[k][0] = fa;
    for(int i = 1; i <= 19; ++i)
    {
        f[k][i] = f[f[k][i - 1]][i - 1];
    }
    dfn[k] = low[k] = ++tot, rk[tot] = k;
    for(auto v : e[k])
    {
        dfs(v, k);
    }
    low[k] = tot;
}

int LCA(int x, int y)
{
    if(dep[x] < dep[y]) swap(x, y);
    for(int i = 19; i >= 0; --i)
    {
        if(dep[f[x][i]] >= dep[y]) x = f[x][i];
    }
    if(x == y) return x;
    for(int i = 19; i >= 0; --i)
    {
        if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    }
    return f[x][0];
}

int cnt[N];

int main()  
{   
    int T = read();
    while(T--)
    {
        n = read(), w = read(), tot = 0;
        for(int i = 1; i <= n; ++i) e[i].clear();
        for(int i = 2; i <= n; ++i)
        {
            int fa = read();
            e[fa].emplace_back(i);
        }
        dfs(1, 0);
        for(int i = 1; i <= n; ++i)
        {
            int v = i % n + 1;
            int lca = LCA(i, v);
            cnt[i] = dep[i] - dep[lca] + dep[v] - dep[lca];
        }
        ll ans = 0, now = w, m = n;
        ll sum = 0;
        for(int i = 1; i < n; ++i)
        {
            int x = read();
            int u = (x == 1) ? (n) : (x - 1);
            ll y = read();
            sum += 2 * y, now -= y;
            --cnt[u];
            if(cnt[u] == 0) --m;
            int v = rk[low[x]];
            --cnt[v];
            if(cnt[v] == 0) --m;
            printf("%lld ", sum + m * now);
        }
        printf("\n");
    }
    return 0;   
}

F. Eri and Expanded Sets

一个数的序列一定是明智的,考虑两个数的序列

要求差为 \(2\) 的次幂

发现实际上是,差为偶数时,还可以再分,直到分成若干的奇数,最终的目的是所有的奇数都是 \(1\)

考虑三个数 \(x, y, z\) ,其中 \(x < y < z\) ,记 \(a = y - x, b = z - y\)

\(a, b\) 有一个是偶数时可以再分,当两个是奇数时 \(a + b = z - x\) 是偶数也可以再分,该分到什么时候?

\(d = \gcd(a, b)\) ,则 \(a = pd, b = qd\)\(\gcd(p, q) = 1\) ,再分出的 \(\frac{a+b}{2^k}\) 一定小于 \(b\) ,且与 \(a\) 的最大公约数仍为 \(d\) ,此时再让它和 \(a\) 相加再分,这样每次取最小的两个数相加再分,一定可以分出 \(d\)

同理可知,一个序列是明智的当且仅当它的差分序列的全体 \(\gcd\)\(2\) 的次幂(此时不必排序好)

区间 \(\gcd\) 是具有单调性的,双指针+st表统计答案

一个坑点是上面讨论的是所有的数都不相同,当相邻的数相同时差分为0,会出错,一个方法是相邻相同的点缩成一个点,统计答案时有点麻烦

点击查看代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

ll gcd(ll x, ll y)
{
    if(!y) return x;
    return gcd(y, x % y);
}

const int N = 4e5 + 5;
int n, a[N], num[N], b[N], sum[N];
int st[N][20], lg[N];

int get(int l, int r)
{
    int d = lg[r - l + 1];
    return gcd(st[l][d], st[r - (1 << d) + 1][d]);
}

int cnt(int x)
{
    int s = 0;
    while(x) s += x & 1, x >>= 1;
    return s;
}

void solve()
{
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    ll ans = n;

    int nn = 1, now = 1;
    for(int i = 2; i <= n; ++i)
    {
        if(a[i] == a[i - 1]) ++now;
        else num[nn] = now, ans += 1ll * now * (now - 1) / 2, now = 1, a[++nn] = a[i];
    }
    num[nn] = now, ans += 1ll * now * (now - 1) / 2;
    for(int i = 1; i <= nn; ++i) sum[i] = sum[i - 1] + num[i];

    for(int i = 2; i <= nn; ++i) b[i] = abs(a[i] - a[i - 1]);
    for(int i = 2; i <= nn; ++i) st[i][0] = b[i];
    for(int j = 1; j <= 19; ++j)
        for(int i = 2; i + (1 << j) - 1 <= nn; ++i)
            st[i][j] = gcd(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
    
    int l = 2, r = 2;
    while(l <= nn && r <= nn)
    {
        r = max(l, r);
        while(r <= nn && cnt(get(l, r)) > 1) ++r;
        ans += 1ll * (n - sum[r - 1]) * num[l - 1], ++l;
    }
    printf("%lld\n", ans);
}

int main()
{
    int T = read();
    lg[0] = -1;
    for(int i = 1; i <= 4e5; ++i) lg[i] = lg[i >> 1] + 1;
    while(T--) solve();
    return 0;
}

Div.1 D. Iris and Adjacent Products

感觉比 \(C\) 还好想,直接贪心就完了

容易发现按照最大值,最小值,次大值,次小值依次放置最优,只需要保证相邻的数乘积不大于 \(K\) 即可,当不满足时将最大值换成 \(1\) ,然后重新排序,判断

\(cnt(l, r, i, j)\) 表示区间 \([l, r]\) 中值域在 \([i, j]\) 之间的数的个数,那么就需要时刻满足 \(cnt(l, r, 1, i) \ge cnt(l, r, \frac{n}{i}, n)\)

如果可以快速计算出 \(cnt(l, r, i, j)\) 就好了

欸,主席树!带个 \(log\) 被卡时间了

欸,可以预处理前缀和 \(cnt\) ,被卡空间了

题解变换了枚举顺序,先枚举 \(cnt(l, r, 1, i)\) 中的 \(i\) ,再枚举每个区间处理,这样空间就不会被卡了

还可以莫队!

发现这是一个 \(n\sqrt{n}\) 次的 \(O(1)\) 移动操作和一个 \(n\) 次的 \(O(\sqrt{n})\) 的统计答案操作

莫队代码
#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 1e5 + 5, M = 1e6 + 5;
int n, Q, K;
int b[N];
int x[1005], be[N], cnt[2005];
int Size, belong[N], ans[N];

struct node
{
    int id, l, r;
    node(){ id = l = r = 0; }
    bool friend operator < (const node &a, const node &b)
    {
        if(belong[a.l ] == belong[b.l ]) return a.r < b.r ;
        else return a.l < b.l ;
    }
}q[N];

void solve()
{
    n = read(), Q = read(), K = read();
    for(int i = 1; i <= n; ++i) belong[i] = 0;
    for(int i = 1; i <= Q; ++i) ans[i] = 0;
    for(int i = 1; i <= n; ++i) b[i] = read();
    Size = sqrt(K);
    for(int i = Size << 1; i >= 1; --i) cnt[i] = 0;
    for(int i = 1; i <= Size; ++i) x[i] = max(K / (i + 1) + 1, Size + 1);
    x[0] = K;
    for(int i = 1; i <= n; ++i)
    {
        if(b[i] <= Size) be[i] = b[i];
        else
        {
            int l = 0, r = Size;
            while(l < r)
            {
                int mid = (l + r + 1) >> 1;
                if(x[mid] <= b[i]) r = mid - 1;
                else l = mid;
            }
            be[i] = l + 1 + Size;
        }
    }
    for(int i = 1; i <= n; i += Size) belong[i] = 1;
    for(int i = 1; i <= n; ++i) belong[i] += belong[i - 1];
    for(int i = 1; i <= Q; ++i) q[i].id = i, q[i].l = read(), q[i].r = read();
    sort(q + 1, q + Q + 1);
    int l = 1, r = 0;
    for(int i = 1; i <= Q; ++i)
    {
        while(l > q[i].l ) --l, ++cnt[be[l]];
        while(r < q[i].r ) ++r, ++cnt[be[r]];
        while(l < q[i].l ) --cnt[be[l]], ++l;
        while(r > q[i].r ) --cnt[be[r]], --r;
        int len = q[i].r - q[i].l + 1, X = 0, Y = 0;
        for(int j = 1; j <= Size; ++j)
        {   
            X += cnt[j], Y += cnt[j + Size];
            if(X + Y == len) ans[q[i].id ] = max(ans[q[i].id ], (Y - X) >> 1); // 后面没数了,最后一个位置可以放大数
            else ans[q[i].id ] = max(ans[q[i].id ], (Y - X + 1) >> 1); // 后面还有数,最后一个位置必须放小数
        }
    }
    for(int i = 1; i <= Q; ++i) printf("%d ", ans[i]);
    printf("\n");
}

int main()
{
    int T = read();
    while(T--) solve();
    return 0;
}

Div.1E. Iris's Full Binary Tree

咕咕咕(大概率不会改了)


Div.1F. Dora's Paint

咕咕咕(大概率不会改了)

posted @ 2024-09-02 10:24  梨愁浅浅  阅读(247)  评论(0编辑  收藏  举报