The 1st Universal Cup. Stage 7: Zaporizhzhia

空気力学の詩發表於2024-08-29

Preface

在寢室白蘭了一週多後也是終於等到徐神歸來開始訓練了

這場的題感覺比較偏數學了,感覺和之前打的一個 Tokyo 的 Open Cup 很像,因此後期挺坐牢的

4h 左右堪堪寫出 7 題,最後全隊 Rush D 結果發現暴力打表都打錯了,怎麼回事呢


A. Square Sum

這題在 去年暑假前集訓數學專題 中做過類似的版本,只不過那個題的模數是沒有平方質因子的奇質數

這題思路應該也是類似,但需要對 \(2\) 的情況進行一些討論,程式碼先坑著有空來補


B. Super Meat Bros

題都沒看,鑑定為不可做


C. Testing Subjects Usually Die

題都沒看,鑑定為不可做


D. Triterminant

感覺思路沒啥問題,就是三步走:解行列式——找合法序列的充要條件——計算最小操作次數

第一步就是線代課後習題的難度,不過推出式子後得到的結論很神秘,然後跑了個暴力打表發現並沒有找到什麼規律

後面看了 WIFI 的程式碼發現似乎是暴力寫掛了(或者是前面搞錯了),需要再反思下


E. Garbage Disposal

分類討論題,首先特判掉 \(L=R\) 的情況

考慮若 \([L,R]\) 中有偶數個數,顯然讓相鄰兩個數兩兩配對一定合法

否則若 \([L,R]\) 中有奇數個數,若此時 \(L\) 為奇數,則可以讓前 \(3\) 個數按:\((L,L+1),(L+1,L+2),(L+2,L)\) 的方式來配對

因為 \((L+2,L)\) 一定互質,而其餘兩組都是相鄰的數因此也必然互質;剩下後面的偶數個數還是類似地兩兩配對即可

否則當 \(L\) 為偶數時一定無解,因為根據鴿籠原理,必然會有至少一對偶數要配對,顯然無解

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t,L,R;
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d%d",&L,&R);
        if (L==R)
        {
            puts(L==1?"1":"-1"); continue;
        }
        if ((R-L+1)%2==0)
        {
            for (RI i=L;i+1<=R;i+=2)
            printf("%d %d ",i+1,i); putchar('\n');
        } else
        {
            if (L%2==0) { puts("-1"); continue; }
            printf("%d %d %d ",L+1,L+2,L);
            for (RI i=L+3;i+1<=R;i+=2)
            printf("%d %d ",i+1,i); putchar('\n');
        }
    }
    return 0;
}

F. Palindromic Polynomial

題都沒看,鑑定為不可做


G. Palindromic Differences

徐神開場寫的,我題目都沒看

#include <bits/stdc++.h>

using llsi = long long signed int;
constexpr llsi mod = 1'000'000'009;

constexpr llsi $n = 500005;

constexpr llsi ksm(llsi a, llsi b) {
    llsi r = 1;
    while(b) {
        if(b & 1) r = r * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return r;
}

llsi fac[$n], facinv[$n], p2[$n];

void prep(int n) {
    fac[0] = 1; p2[0] = 1;
    for(int i = 1; i <= n; ++i) p2[i] = p2[i - 1] * 2 % mod;
    for(int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
    facinv[n] = ksm(fac[n], mod - 2);
    for(int i = n; i >= 1; --i) facinv[i - 1] = facinv[i] * i % mod;
    return ;
}

int n, a[$n];

void work() {
    std::cin >> n;
    for(int i = 1; i <= n; ++i) std::cin >> a[i];
    std::sort(a + 1, a + n + 1);
    llsi ans = fac[n / 2];
    int cnt = 1;
    for(int i = 2, j = n - 1; ; ++i, --j) {
        if(a[i] + a[j] != a[i - 1] + a[j + 1]) {
            ans = 0;
            break;
        }
        if(i < j && a[i] == a[i - 1]) cnt++;
        else {
            ans = ans * facinv[cnt] % mod;
            if(a[i - 1] != a[j + 1]) ans = ans * p2[cnt] % mod;
            cnt = 1;
        }
        if(i >= j) break;
    }
    if((n & 1) && a[n / 2 + 1] * 2 != a[1] + a[n]) ans = 0;
    std::cout << ans << char(10);
    // if(n == 14) std::cout << fac[7] * p2[7] % mod << char(10);
    return ;
}

int main() {
    std::ios::sync_with_stdio(false);
    prep(500000);
    int T; std::cin >> T; while(T--) work();
    return 0;
}


H. Graph Isomorphism

神秘分類討論題

注意到和一個圖同構的圖數量在沒什麼特殊限制時大機率是 \(n!\) 級別的,因此當 \(n\) 足夠大時絕大多數圖都是無解的

手玩一波會發現菊花圖因為有一個 fixed point 的存在,因此恰好有 \(n\) 種同構圖

與此同時比較隱蔽的還有菊花圖的補圖,即一個孤點加上一個完全圖也是合法的

另外還有些特殊情況,例如點數小於等於 \(3\) 的圖、完全圖,最坑的是當 \(n=4\) 時四元環和每個點度數為 \(1\) 的圖也是有解的

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,m,x,y,deg[N];
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d%d",&n,&m);
        for (RI i=1;i<=n;++i) deg[i]=0;
        for (RI i=1;i<=m;++i)
        scanf("%d%d",&x,&y),++deg[x],++deg[y];
        if (n<=3) { puts("YES"); continue; }
        if (n==4&&m==2&&deg[1]==1&&deg[2]==1&&deg[3]==1&&deg[4]==1) { puts("YES"); continue; }
        if (n==4&&m==4&&deg[1]==2&&deg[2]==2&&deg[3]==2&&deg[4]==2) { puts("YES"); continue; }
        if (m==1LL*n*(n-1)/2) { puts("YES"); continue; }
        int center=0,outer=0;
        for (RI i=1;i<=n;++i)
        if (deg[i]==n-1) ++center; else if (deg[i]==1) ++outer;
        if (center==1&&outer==n-1) { puts("YES"); continue; }
        int iso=0,kernel=0;
        for (RI i=1;i<=n;++i)
        if (deg[i]==0) ++iso; else if (deg[i]==n-2) ++kernel;
        if (iso==1&&kernel==n-1) { puts("YES"); continue; }
        puts("NO");
    }
    return 0;
}

I. DAG Generation

不會 Counting 怎麼辦,我們只需要暴力打表並找規律即可輕鬆過題

首先計算相同的機率顯然更簡單,同時不妨把生成圖的方式稍作變換,先隨機固定一個排列,然後欽定邊只能從前面的點連向後面的點,不難發現這種生產方式和原題的表述等價

考慮總的方案數作為分母是 \([n!\times 2^{\frac{n(n-1)}{2}}]^2\),現在我們要對每一種具體的方案去重後平方累計貢獻得到分子

寫一個暴力會發現分子形如 \(n!\times (2^1-1)\times (2^2-1)\times (2^n-1)\),因此最後答案的表示式即為:

\[1-\frac{(2^1-1)\times (2^2-1)\times (2^n-1)}{n!\times 2^{n(n-1)}} \]

#include <bits/stdc++.h>

using llsi = long long signed int;
constexpr llsi mod = 1'000'000'009;

constexpr llsi $n = 500005;

constexpr llsi ksm(llsi a, llsi b) {
    llsi r = 1;
    while(b) {
        if(b & 1) r = r * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return r;
}

llsi fac[$n], facinv[$n], p2[$n], hkr[$n];

void prep(int n) {
    fac[0] = 1; p2[0] = 1; hkr[0] = 1;
    for(int i = 1; i <= n; ++i) p2[i] = p2[i - 1] * 2 % mod;
    for(int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
    facinv[n] = ksm(fac[n], mod - 2);
    for(int i = n; i >= 1; --i) facinv[i - 1] = facinv[i] * i % mod;
    return ;
}

int main() {
    std::ios::sync_with_stdio(false);
    prep(100000);
    hkr[1] = 1;
    for(int i = 2; i <= 100000; ++i) hkr[i] = hkr[i - 1] * (p2[i] - 1) % mod;
    int T; std::cin >> T; while(T--) {
        llsi n; std::cin >> n;
        llsi ans = hkr[n] * fac[n] % mod;
        ans = ans * ksm(fac[n] * ksm(2, n * (n - 1) / 2) % mod, (mod - 2) * 2) % mod;
        std::cout << (1 + mod - ans) % mod << char(10);
    }
}

J. Persian Casino

小清新 Counting 題,想清楚策略後就很簡單了

注意到最優策略一定是先一直做,直到 rollback 的次數全部耗盡;接下來的部分怎麼操作都不會改變答案的期望了

因此最壞情況下,若 \(2^m<n-m\),即前 \(m\) 輪就用完了 rollback,此時積攢的錢不夠後面每輪投一塊錢,此時無解

否則必然有解,考慮計算期望,不難發現最後的錢數只和倍增的輪次有關,不妨列舉最後恰好倍增了 \(k\)

分情況討論,當 \(k<n\) 時,這種情形的機率為 \(C_{m-1}^{k-1}\times \frac{1}{2^k}\),錢數為 \(2^k\),因此總貢獻為 \(\sum_{k=m}^{n-1} C_{m-1}^{k-1}=C_n^m\)

\(k=n\) 時,改為列舉一共用了多少次 rollback,總貢獻為 \(\sum_{i=0}^{m-1} C_n^i\times \frac{1}{2^n}\times 2^n=\sum_{i=0}^{m-1} C_n^i\)

因此最後合併一下發現答案就是 \(\sum_{i=0}^{m} C_n^i\),計算十分容易

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,mod=1e9+9;
int t,n,m,fact[N],ifac[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
    for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
    fact[0]=1; for (RI i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
    ifac[n]=quick_pow(fact[n]); for (RI i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
    if (n<0||m<0||n<m) return 0;
    return 1LL*fact[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int main()
{
    for (scanf("%d",&t),init(1e5);t;--t)
    {
        scanf("%d%d",&n,&m);
        if (m<=20&&(1<<m)<n-m) { puts("bankrupt"); continue; }
        int ans=0; for (RI i=0;i<=m;++i) (ans+=C(n,i))%=mod;
        printf("%d\n",ans);
    }
    return 0;
}

K. Determinant, or...?

計算行列式的值的經典思路就是轉為上/下三角矩陣後把對角線乘起來,因此考慮消元

不難發現原矩陣可以等分為四個部分,其中除左上角區域外的三個部分內的數完全相同

考慮把矩陣的上半部分減去下半部分,這樣就可以把右上角部分全部變為 \(0\)

而左上角部分不難發現扔滿足上述性質,遞迴處理即可將整個行列式變為下三角矩陣,總複雜度 \(O(n\log n)\)

#include <bits/stdc++.h>

constexpr int mod = 1'000'000'009;
int a[1 << 20];

int64_t calc(int l, int r) {
    if(r == l + 1) return a[l];
    int mid = (l + r) >> 1;
    for(int i = l; i < mid; ++i) a[i] = (a[i] + mod - a[i + r - mid]) % mod;
    return calc(l, mid) * calc(mid, r) % mod;
}

int main() {
    std::ios::sync_with_stdio(false);
    int n; std::cin >> n; n = 1 << n;
    for(int i = 0; i < n; ++i) std::cin >> a[i];
    std::cout << calc(0, n) << char(10);
    return 0;
}

L. Directed Vertex Cacti

題都沒看,鑑定為不可做


M. Siteswap

祁神 solo 的一個神秘模擬題(?),我連題意都看不懂,好像細節挺多很容易寫掛

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

const int N = 1e5+5;
int n, A[N];
bool vis[N];

void solve(){
    cin >> n;
    for (int i=0; i<n; ++i) cin >> A[i], vis[i]=false;
    int ans[3] = {0, 0, 0};

    for (int i=0; i<n; ++i) if (!vis[i]){
        if (0==A[i]){
            vis[i]=true; 
            continue;
        }

        if (A[i]%n==0){
            int res = A[i]/n;
            if (n%2==0) ans[i%2] += res;
            else{
                if (res%2==0){
                    ans[i%2] += (res+1)/2;
                    ans[(i+1)%2] += res/2;
                }else ans[2] += res;
            }
            continue;
        }

        int loops = 0;
        bool odd = false;
        int cur = i;
        while (!vis[cur]){
            int res = (cur+A[cur])/n;
            loops += res;
            if (A[cur]%2==1) odd=true;
            vis[cur]=true;
            cur = (cur+A[cur])%n;
        }

        if (!odd){
            // printf("n%2=%lld\n", n %2);
            if (n%2==0) ans[i%2] += loops;
            else{
                if (loops%2==0){
                    ans[i%2] += (loops+1)/2;
                    ans[(i+1)%2] += loops/2;
                }else ans[2] += loops;
            }
        }else ans[2] += loops;
    }

    cout << ans[0] << ' ' << ans[1] << ' ' << ans[2] << '\n';
}

signed main(){
    ios::sync_with_stdio(0); cin.tie(0);
    int t; cin >> t; while (t--) solve();
    return 0;
}

Postscript

感覺這場題都不太對我們隊的好球區啊,這斯拉夫人怎麼和日本人一樣喜歡出一堆數學題呢

相關文章