第八届中国大学生程序设计竞赛总决赛(CCPC Final 2022)

Preface

真是坐坐又牢牢啊,2h40min 过了 A 后就开始发呆,剩下的题一个也做不来

但好在会的几个题全部一遍过,最后罚时尚可还能混进 Au 区


A. Graph Partitioning

考虑一条连接 \(x,y(x<y)\) 的边,显然它可以确定 \(T_1\)\(y\) 的父亲,也可以确定 \(T_2\)\(x\) 的父亲

因此构建一个二分图,左边是 \(2n-2\) 条边对应的点,右边是两棵树中 \(2n-2\) 个需要确定父亲节点的点,左边的每个点都会连接右边的两个点

不难发现有解的充要条件是这张图存在完美匹配,而稍做分析会发现由于这个二分图的点数等于边数,因此有完美匹配的充要条件为每个连通块都是基环树

而基环树的完美匹配方案一定为 \(2\)(环上某条边的匹配确定后,剩下的都唯一确定,一共有两个方向),因此最后方案数就是 \(2\) 的连通块幂次

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=2000005,mod=998244353;
int n,x,y,fa[N],num[N],sz[N];
inline int getfa(CI x)
{
    return fa[x]!=x?fa[x]=getfa(fa[x]):x;
}
inline void Link(CI x,CI y)
{
    fa[getfa(x)]=getfa(y);
}
int main()
{
    scanf("%d",&n);
    if (n==1) return puts("1"),0;
    for (RI i=1;i<=4*n-4;++i) fa[i]=i;
    for (RI i=1;i<=2*n-2;++i)
    {
        scanf("%d%d",&x,&y);
        if (x==y) return puts("0"),0;
        if (x>y) swap(x,y);
        Link(i,2*n-2+y-1); Link(i,3*n-3+x);
    }
    for (RI i=1;i<=4*n-4;++i) ++sz[getfa(i)];
    for (RI i=1;i<=2*n-2;++i) num[getfa(i)]+=2;
    // for (RI i=1;i<=4*n-4;++i) printf("%d%c",sz[i]," \n"[i==4*n-4]);
    // for (RI i=1;i<=4*n-4;++i) printf("%d%c",num[i]," \n"[i==4*n-4]);
    int cnt=0;
    for (RI i=1;i<=4*n-4;++i)
    {
        if (getfa(i)!=i) continue;
        if (num[i]!=sz[i]) return puts("0"),0; else ++cnt;
    }
    int ans=1;
    for (RI i=1;i<=cnt;++i) ans=2LL*ans%mod;
    return printf("%d",ans),0;
}

C. DFS Order 3

这题之前在 2023 合肥区域赛的热身赛上做过,这次就直接秒了

#include <bits/stdc++.h>

void work() {
    int n; std::cin >> n;

    std::vector<std::vector<int>> d(n, std::vector<int>(n));

    for(auto &d: d) for(auto &d: d) std::cin >> d, d--;

    std::vector<std::pair<int, int>> ans;
    std::vector<bool> vis(n, false);

    vis[0] = true;
    for(int i = 1; i < n; ++i) {
        int cur = d[0][i];

        for(int j = 0; j < n; ++j) if(vis[d[cur][j]]) {
            ans.push_back({cur, d[cur][j]});
            break;
        }
        vis[cur] = true;
    }

    for(auto [f, t]: ans) std::cout << f + 1 << " " << t + 1 << char(10);
    return ;
}

int main() {
    std::ios::sync_with_stdio(false);

    int T; std::cin >> T; while(T--) work();
    return 0;
}


E. CCPC String

枚举中心的 \(P\) 所在的位置,显然两边最长能延申的距离是可以二分的

#include<cstdio>
#include<iostream>
#include<cstring>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int t,pfx[N]; char s[N];
signed main()
{
    for (scanf("%lld",&t);t;--t)
    {
        scanf("%s",s+1); int n=strlen(s+1),ans=0;
        for (RI i=1;i<=n;++i) pfx[i]=pfx[i-1]+(s[i]!='p'?1:0);
        for (RI i=1;i<=n;++i)
        {
            if (s[i]=='c') continue;
            int l=1,r=min((i-1)/2,n-i),ret=0;
            auto check=[&](CI l,CI r)
            {
                return pfx[r]-pfx[l-1]==r-l+1;
            };
            while (l<=r)
            {
                int mid=l+r>>1;
                if (check(i-mid*2,i-1)&&check(i+1,i+mid)) ret=mid,l=mid+1; else r=mid-1;
            }
            ans+=ret;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

F. Chase Game 3

如果存在某个 \(i,i+1\),使得它们在排列上的距离 \(\ge 3\),此时后手一定不能获胜

因为此时 \(i,i+1\) 两个点在排列上的邻居一定不同,先手只要在 \(i,i+1\) 之间反复走到与后手当前位置不相邻的点即可

#include<bits/stdc++.h>
using namespace std;

const int N = 4e5+5;
int pos[N];

bool solve() {
    int n; cin >> n;
    for (int i=1; i<=n; ++i) {
        int a; cin >> a;
        pos[a] = i;
    }
    for (int i=1; i<n; ++i) {
        if (abs(pos[i]-pos[i+1]) > 2) return false;
    }
    return true;
}

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T; while (T--) cout << (solve() ? "Yes\n" : "No\n");
    return 0;
}

J. Best Carry Player 3

考虑找到 \(k\) 二进制下的最高位 \(t\),显然异或操作只会改变后 \(t\)

因此最优的策略大致为:先用异或操作将最后 \(t\) 为变为全 \(1\),再通过 \(+1\) 把前面的部分增加

但具体实现时需要注意各种 Corner Case,比如 \(k=2^t-1\) 时可以两步变换,否则至少要三步;还有一些边界情况的处理

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
int t,x,y,k;
signed main()
{
    for (scanf("%lld",&t);t;--t)
    {
        scanf("%lld%lld%lld",&x,&y,&k);
        if (x>y) swap(x,y);
        if (x==y) { puts("0"); continue; }
        if (k<=1) { printf("%lld\n",y-x); continue; }
        int hb=1; while (hb<=k) hb<<=1;
        int xf=x/hb,xb=x%hb,yf=y/hb,yb=y%hb,ans=0;
        auto calc=[&](CI x,CI y)
        {
            if (x==y) return 0;
            return ((x^y)<=k||y-x<=1)?1:2;
        };
        if (xf==yf)
        {
            printf("%lld\n",calc(xb,yb));
            continue;
        }
        if (xb==(hb-1)) ans+=1; else
        if (((hb-1)^xb)<=k) ans+=2; else ans+=3;
        ++xf; xb=0;
        if (xf!=yf) ans+=(yf-xf)*(k==hb-1?2:3),xf=yf;
        ans+=calc(xb,yb);
        printf("%lld\n",ans);
    }
    return 0;
}

L. Completely Multiplicative Function

徐神在我的疯狂质疑下还是秒了这题,令人感慨

首先假设所有位置初始时都是 \(1\),现在考虑要将若干位置变成 \(-1\)

不难发现 \([\frac{n}{2},n]\) 内的所有素数的值都可以任意改变,这就给了我们一个调整的余量

而对于 \([1,\frac{n}{2}]\) 内的素数显然先修改小的能改变的位置数更多,我们直接爆搜,在有余量调整的情况下跑的飞快

#include <bits/stdc++.h>

using llsi = long long signed int;

constexpr bool check = false;

bool is_not_prime[1000005];
std::vector<int> prime;

void init(int n) {
    is_not_prime[0] = is_not_prime[1] = 1;
    for(int i = 2; i <= n; ++i) if(!is_not_prime[i]) {
        prime.push_back(i);
        for(llsi j = llsi(i) * i; j <= n; j += i) is_not_prime[j] = 1;
    }
    return ;
}

void work(int n, int k) {
    if(n % 2 != k % 2) return std::cout << "-1\n", void(0);

    k = (n - k) / 2;
    std::vector<bool> _b(n + 1, 0);
    const std::vector<bool> &b = _b;

    int cur = 0;
    auto upd = [&](int pos, bool newVal) {
        cur -= _b[pos];
        cur += _b[pos] = newVal;
    };

    int hkr = 0;
    for(int i = (n + 2) / 2; i <= n; ++i) if(!is_not_prime[i]) hkr++;

    if(hkr < k) for(auto pr: prime) {
        if(pr >= (n + 2) / 2) return void(std::cerr << "FUCK\n"), void(std::cout << "-1\n");

        for(llsi P = pr; P <= n; P *= pr)
            for(llsi i = 1; i <= n / P; ++i)
                upd(i * P, b[i * P] ^ 1);
        if(cur > k) {
            for(llsi P = pr; P <= n; P *= pr)
                for(llsi i = 1; i <= n / P; ++i)
                    upd(i * P, b[i * P] ^ 1);
        } else
        if(cur + hkr >= k) {
            break;
        }
    }

    // std::cerr << "cur = " << cur << char(10);

    for(int i = (n + 2) / 2; cur < k && i <= n; ++i)
        if(!is_not_prime[i]) upd(i, b[i] ^ 1);

    if constexpr (check) {
        for(int i = 1; i <= n; ++i) for(int j = 1; i * j <= n; ++j) {
            if((b[i] ^ b[j]) != b[i * j]) {
                std::cerr << "n, k = " << n << ", " << k << " failed at " << i << " * " << j << char(10);
                goto __break__;
            }
        }
__break__:;
    }
    
    for(int i = 1; i <= n; ++i) std::cout << (b[i] ? "-1" : "1") << char(i == n ? 10 : 32);
    return ;
}

int main() {
    std::ios::sync_with_stdio(false);
    init(1000000);
    // for(int i = 0; i <= 1000; ++i) for(int k = (i & 1); k <= i; k += 2) work(i, k);
    // return 0;
    int T; std::cin >> T; while(T--) {
        int n, k;
        std::cin >> n >> k;
        work(n, k);
    }
    return 0;
}

Postscript

唉沟槽的 Final,感觉下个月要在场上狠狠罚坐了

posted @ 2025-02-21 16:48  空気力学の詩  阅读(388)  评论(0)    收藏  举报