OI Memory 后記——「新征程、就從明天開始」


2021.7.31

上午把回憶錄寫完了,不太想看深度學習,就搞搞諤諤模擬賽。

2021.8.6

滾來參加 tx 的一個活動,zjk、cyx、cxr 都來了,鄧老師被請過來裝學員 /kx

下午坐高鐵到了深圳,等 zz 等了 1h,就滾去酒店了。

晚上搞了一些諤諤的團建活動,但通信題還是比超腦好玩多了,雖然我啥都沒干的團隊任務又讓我意識到了我是個 fw 的事實。

題目就是通過 10 根繩子傳遞長度不超過 10 的字符串,字符可能是小寫字母、數字、數學符號、常見漢字。

鄧老師造的編碼方法是一個繩子表示一個字符,穿一次的結表示 0,穿兩次的結表示 1,類型用 2bit,然后直接對漢字之外的字符編碼,漢字直接寫拼音。

zz 他們隊的編碼方法要劣一些,但他們打結技術高超所以 rk1 了,ntf 他們隊也 rk2 了,我們即使有鄧老師帶還是墊底了。

2021.8.7

聽了一天課,無聊死了。

晚上坐雙層巴士亂逛,導游的聲音有點 /tuu。

2021.8.12

總體來說體驗很差,因為實在不想做任務。

終於熬到結束可以放假了。

2021.9.1

上午無聊就在數競室看小藍皮(

ARC122E Increasing LCMs

給定長為 \(n\) 的正整數列 \(x_i\),求重排方案使得前綴 LCM 遞增。

\(n\le 100\)\(x_i\le 10^{18}\)


從后往前考慮,對於末尾的數 \(x_i\),要滿足 \(x_i\nmid\text{LCM}_{j\ne i}(x_j)\)

為了方便判斷考慮轉化一下:\(\text{LCM}_{j\ne i}(\gcd(x_j,x_i))<x_i\)

同時對於可以放在末尾的數 \(x_i\),它放在前面不會更優,於是就放末尾了。

暴力判斷,時間復雜度 \(O(n^3\log V)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 103;
int n;
LL a[N], ans[N];
bool vis[N];
LL gcd(LL a, LL b){return b ? gcd(b, a % b) : a;}
LL lcm(LL a, LL b){return a / gcd(a, b) * b;}
int main(){
    ios::sync_with_stdio(false);
    cin >> n;
    for(int i = 1;i <= n;++ i) cin >> a[i];
    for(int p = n;p;-- p){
        bool flg = false;
        for(int i = 1;i <= n;++ i) if(!vis[i]){
            LL tmp = 1;
            for(int j = 1;j <= n;++ j)
                if(!vis[j] && i != j) tmp = lcm(tmp, gcd(a[j], a[i]));
            if(tmp < a[i]){ans[p] = a[i]; vis[i] = flg = true; break;}
        }
        if(!flg){puts("No"); return 0;}
    }
    puts("Yes");
    for(int i = 1;i <= n;++ i) printf("%lld ", ans[i]);
}

ARC123E Training

給定正整數 \(a_1,a_2,b_1,b_2,n\),求

\[\sum_{i=1}^n\left[a_1+\lfloor\frac i{b_1}\rfloor=a_2+\lfloor\frac{i}{b_2}\rfloor\right] \]

\(T\) 組數據。\(T\le 2\cdot 10^5\)\(n\le 10^9\)\(a_1,a_2,b_1,b_2\le 10^6\)


先轉化為 \(\lfloor i/b_1\rfloor-\lfloor i/b_2\rfloor=a_2-a_1\),設為 \(a\)

先特判掉 \(b_1=b_2\) 的情況,不妨設 \(b_1<b_2\)

顯然可以先圈一圈范圍:\(i/b_1-i/b_2\in(-1,1]\)

亂搞一下,分成 \(i/b_1-i/b_2\in(-1,0]\)\(i/b_1-i/b_2\in(0,1]\)。此時計算一下 \(\sum\lfloor i/b_1\rfloor-\sum\lfloor i/b_2\rfloor\) 就可以求出對應的答案。

時間復雜度 \(O(T)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int T;
LL n, a, b, c, d;
LL calc(LL a, LL b){LL c = a / b; return (c*(c-1ll)>>1)*b + (a-b*c)*c;}
LL calc(LL l, LL r, LL a, LL b){return a*(r-l) + calc(r,b) - calc(l,b);}
int main(){
    ios::sync_with_stdio(false);
    cin >> T;
    while(T --){
        cin >> n >> a >> b >> c >> d;
        if(b == d){printf("%lld\n", a == c ? n : 0); continue;}
        if(b > d){swap(a, c); swap(b, d);} c -= a;
        LL ans = 0, l = max(b*d*(c-1)/(d-b),0ll) + 1, r = min(b*d*c/(d-b),n) + 1;
        if(l < r) ans = r - l + calc(l, r, 0, b) - calc(l, r, c, d);
        l = max(b*d*c/(d-b),0ll) + 1; r = min(b*d*(c+1)/(d-b),n) + 1;
        if(l < r) ans += r - l + calc(l, r, c, d) - calc(l, r, 0, b);
        printf("%lld\n", ans);
    }
}

ARC124E Pass to Next

給定 \(n\) 個正整數,求

\[\sum_{x_i\le a_i \\ \min x_i=0}\prod_{i=1}^n(a_i-x_i+x_{i-1})\bmod 998244353 \]

\(n\le 10^5\)\(a_i\le 10^9\)


\(\min x_i=0\) 的限制很容易去掉,只需要減去限制傳球數 \(>0\) 的情況即可。

考慮組合意義,傳完球后每個人要取出自己的一個球。

因為每個人的傳球數互相獨立,所以對每個人取出球的來源做 dp。設 \(f_{i,0}\) 表示第 \(i\) 個人取出自己的球的情況下,前 \(i-1\) 個人取球的方案數,\(f_{i,1}\) 表示第 \(i\) 個人取出第 \(i-1\) 個人給他的球的情況下,前 \(i\) 個人取球的方案數。

\(S_k(n)=\sum_{i=0}^ni^k\),則可以列出轉移式:

\[\begin{aligned} f_{i+1,0}&=S_1(a_i)\cdot f_{i,0}+(a_i+1)\cdot f_{i,1} \\ f_{i+1,1}&=(a_iS_1(a_i)-S_2(a_i))\cdot f_{i,0}+S_1(a_i)\cdot f_{i,1} \end{aligned} \]

如果限制傳球數 \(>0\) 就將第一個柿子的 \(a_i\) 減去 \(1\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100003, mod = 998244353, inv3 = (mod+1)/3;
int n, a[N], f[N][2], ans;
void qmo(int &x){x += x >> 31 & mod;}
int calc(bool f1, bool f2){
    memset(f, 0, n+1<<3); f[0][f1] = 1;
    for(int i = 0;i < n;++ i){
        int b = a[i]-f2, c = (b*(b+1ll)>>1)%mod;
        f[i+1][0] = ((LL)c*f[i][0]+(b+1ll)*f[i][1])%mod;
        b += f2; c = (b*(b+1ll)>>1)%mod;
        int d = c*(b-1ll)%mod*inv3%mod;
        f[i+1][1] = ((LL)c*f[i][1]+(LL)d*f[i][0])%mod;
    } return f[n][f1];
}
int main(){
    ios::sync_with_stdio(false);
    cin >> n;
    for(int i = 0;i < n;++ i) cin >> a[i];
    qmo(ans += calc(0,0)+calc(1,0)-mod);
    qmo(ans -= calc(0,1)); qmo(ans -= calc(1,1));
    printf("%d\n", ans);
}

ARC125E Snack

給定 \(n\) 種零食,第 \(i\) 種零食有 \(a_i\) 份。有 \(m\) 個小朋友分零食,第 \(i\) 個小朋友至多得到 \(c_i\) 份零食,至多得到 \(b_i\) 份同一種零食。求最多可以分多少份零食。

\(n,m\le2\cdot 10^5\)\(a_i,c_i\le 10^{12}\)\(b_i\le 10^7\)


這顯然是最大流模型,轉化為最小割之后直接做。時間復雜度 \(O(n\log n+m)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200003;
template<typename T>
bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, m;
LL a[N], b[N], c[N], d[N], e[N], tmp1, tmp2, ans = 1e18;
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for(int i = 1;i <= n;++ i) cin >> a[i];
    for(int i = 1;i <= m;++ i) cin >> b[i];
    for(int i = 1;i <= m;++ i){
        cin >> c[i]; tmp2 += c[i];
        int x = max(n - c[i] / b[i], 0ll);
        d[x] += b[i]; e[x] += c[i];
    }
    sort(a + 1, a + n + 1);
    for(int i = 0;i <= n;++ i){
        tmp2 += a[i] - e[i]; tmp1 += d[i];
        chmin(ans, tmp2 + tmp1 * (n-i));
    }
    printf("%lld", ans);
}

ARC125F Tree Degree Subset Sum

給定 \(n\) 個點的樹,求有多少對二元組 \((x,y)\),滿足存在一種選出 \(x\) 個點的方案,使得度數之和為 \(y\)

\(n\le 2\cdot 10^5\)


實際上這棵樹並沒有什么用,我們只需要度數序列 \(d_1,d_2,\cdots,d_n\),滿足 \(\sum d_i=2n-2\)\(d_i\ge 1\)。除了這個性質之外就沒有別的了。

所以考慮隨便打表猜結論,然后什么都看不出來。

亂搞一下,發現把 \(d_i\) 都減去 \(1\) 沒什么區別,於是就繼續打表。

發現若 \(m(s),M(s)\) 分別表示選出 \(d_i\) 之和為 \(s\) 的點數的最小值和最大值,則 \([m(s),M(s)]\) 的點數都可以選出來。

如果這個結論是對的,那么就不用對點數記狀態了,直接大力背包即可。時間復雜度 \(O(n\sqrt n)\)

題解告訴我們這個結論確實是對的。考慮設 \(z=\sum[d_i=0]\),則對於任意子集 \(S\subseteq [n]\)\(-z\le \text{sum}(S)-|S|\le z-2\),所以 \(M(s)-m(s)\le 2z-2\),而 \(m(s)\) 的方案不選 \(0\),如果選上可以得到 \([m(s),m(s)+z]\) 的方案,\(M(s)\) 同理可得 \([M(s)-z,M(s)]\) 的方案,得證。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200003;
int n, z, d[N], f[N];
LL ans;
template<typename T>
bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
template<typename T>
bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
int main(){
    ios::sync_with_stdio(false);
    cin >> n;
    memset(f, 0x3f, sizeof f); f[0] = 0;
    for(int i = 0;i < n;++ i) d[i] = -1;
    for(int i = n-1<<1, x;i;-- i){cin >> x; ++ d[x-1];}
    sort(d, d + n);
    for(int i = 0, j;i < n;i += j){
        for(j = 0;d[i+j] == d[i];++ j);
        int rem = j;
        for(int k = 1;k <= rem;rem -= k, k <<= 1){
            int val = d[i]*k;
            for(int i = n-2;i >= val;-- i)
                chmin(f[i], f[i-val]+k);
        }
        if(rem){
            int val = d[i]*rem;
            for(int i = n-2;i >= val;-- i)
                chmin(f[i], f[i-val]+rem);
        }
    }
    for(int i = 0;i < n-1;++ i) ans += max(0, n+1-f[n-2-i]-f[i]);
    printf("%lld\n", ans);
}

CF1556F Sports Betting

給定 \(n\) 個點的競賽圖,每個點 \(i\) 有點權 \(a_i\)\(i\to j\) 的概率是 \(\frac{a_i}{a_i+a_j}\),求能直接或間接到達所有點的點數期望值\(\bmod(10^9+7)\)

\(n\le 14\)\(a_i\le 10^6\)


根據經典套路,競賽圖縮點之后是鏈,所以直接枚舉入度為 \(0\) 的強連通分量 \(S\),計算 \(|S|f_Sg_{S,V\backslash S}\) 之和,其中 \(f_S\) 表示 \(S\) 是強連通圖的概率,\(g_{S,T}=\prod_{i\in S}\prod_{j\in T}\frac{a_i}{a_i+a_j}\) 表示所有 \(S,T\) 之間的邊的方向都是 \(S\to T\)

先考慮計算 \(f_S\),直接容斥,如果不是強連通圖,枚舉入度為 \(0\) 的強連通分量 \(T\),則有

\[f_S=1-\sum_{T\subseteq S}f_Tg_{T,S\backslash T} \]

\(g\) 的時候先對所有 \((x,T)\) 預處理 \(\prod_{y\in T}a_x/(a_x+a_y)\) 就可以做到 \(O(n3^n)\)

題解是 \(O(3^n)\) 做法。直接折半,分別將 \(S,T\) 拆成兩部分得到四項,算 \(g\) 的復雜度就直接降到 \(\text{soft-}O(2^n)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1<<14, mod = 1e9+7;
int n, n1, n2, l1, l2, lim, a[14], e[14][14], f[4][128][128], dp[N], ans;
void qmo(int &x){x += x >> 31 & mod;}
int ksm(int a, int b){
    int res = 1;
    for(;b;b >>= 1, a = (LL)a * a % mod)
        if(b & 1) res = (LL)res * a % mod;
    return res;
}
int calc(int S, int T){
    return (LL)f[0][S&l1-1][T&l1-1]*f[1][S&l1-1][T>>n1]%mod*f[2][S>>n1][T&l1-1]%mod*f[3][S>>n1][T>>n1]%mod;
}
int main(){
    ios::sync_with_stdio(false);
    cin >> n; n1 = n>>1; n2 = n+1>>1; l1 = 1<<n1; l2 = 1<<n2; lim = 1<<n;
    for(int i = 0;i < n;++ i){
        cin >> a[i];
        for(int j = 0;j < i;++ j){
            e[i][j] = (LL)a[i]*ksm(a[i]+a[j],mod-2)%mod;
            qmo(e[j][i] = mod+1-e[i][j]);
        }
    }
    for(int S = 0;S < l1;++ S)
        for(int T = 0;T < l1;++ T) if(!(S&T)){
            f[0][S][T] = 1;
            for(int i = 0;i < n1;++ i) if(S>>i&1)
                for(int j = 0;j < n1;++ j) if(T>>j&1)
                    f[0][S][T] = (LL)f[0][S][T] * e[i][j] % mod;
        }
    for(int S = 0;S < l2;++ S)
        for(int T = 0;T < l2;++ T) if(!(S&T)){
            f[3][S][T] = 1;
            for(int i = 0;i < n2;++ i) if(S>>i&1)
                for(int j = 0;j < n2;++ j) if(T>>j&1)
                    f[3][S][T] = (LL)f[3][S][T] * e[i+n1][j+n1] % mod;
        }
    for(int S = 0;S < l1;++ S)
        for(int T = 0;T < l2;++ T){
            f[1][S][T] = 1;
            for(int i = 0;i < n1;++ i) if(S>>i&1)
                for(int j = 0;j < n2;++ j) if(T>>j&1)
                    f[1][S][T] = (LL)f[1][S][T] * e[i][j+n1] % mod;
        }
    for(int S = 0;S < l2;++ S)
        for(int T = 0;T < l1;++ T){
            f[2][S][T] = 1;
            for(int i = 0;i < n2;++ i) if(S>>i&1)
                for(int j = 0;j < n1;++ j) if(T>>j&1)
                    f[2][S][T] = (LL)f[2][S][T] * e[i+n1][j] % mod;
        }
    for(int i = 1;i <= n;++ i){
        for(int S = 1;S < lim;++ S) if(__builtin_popcount(S) == i){
            dp[S] = 1;
            for(int T = S&S-1;T;T = T-1&S)
                qmo(dp[S] -= (LL)dp[T]*calc(T,S-T)%mod);
            ans = (ans + (LL)dp[S]*i%mod*calc(S,lim-1-S)) % mod;
        }
    }
    printf("%d\n", ans);
}

2021.9.2

ARC124F Chance Meeting

給定 \(n\times m\) 的網格圖,初始時駱駝在 \((1,1)\),貓貓在 \((n,1)\),每次操作駱駝可以向右/下走,貓貓可以往右/上走。求滿足以下條件的操作序列個數\(\bmod 998244353\)

  • 駱駝最后走到 \((n,m)\),貓貓最后走到 \((1,m)\)
  • 存在恰好一個操作,這個操作之后駱駝和貓貓在同一個格子上。

\(2\le n,m\le 2\cdot 10^5\)


完全不會,滾去看題解了

先將 \(n,m\) 都減去 \(1\),即兩者都需做 \(n\) 次豎直移動和 \(m\) 次水平移動。

發現只考慮豎直移動時,不關心移動兩者中的哪個,因為它們在同一行當且僅當進行了 \(n\) 次豎直移動。

\(f_i\) 表示兩者在 \(i\) 次水平移動之后第一次相遇的方案數,則答案是 \(\binom{2n}{n}\sum_{i=0}^mf_if_{m-i}\)

\(f\) 是經典容斥,設 \(g_i\) 表示兩者在 \(i\) 次水平移動之后相遇的方案數,則 \(g_i=\frac{(2i+n)!}{i!^2n!}\),枚舉兩者倒數第二次相遇的位置 \(j\) 就得到:

\[f_i=g_i-2\sum_{j=0}^{i-1}g_jC_{i-j-1} \]

其中 \(C_i\) 是 Catalan 數,所以做一次卷積就算出來了,時間復雜度 \(O(n+m\log m)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1<<19, M = 600003, mod = 998244353;
int n, m, lim, A[N], B[N], C[N], rev[N], w[N], fac[M], inv[M], ans;
int ksm(int a, int b){
    int res = 1;
    for(;b;b >>= 1, a = (LL)a * a % mod)
        if(b & 1) res = (LL)res * a % mod;
    return res;
}
void init(int n){
    fac[0] = 1;
    for(int i = 1;i <= n;++ i) fac[i] = (LL)fac[i-1] * i % mod;
    inv[n] = ksm(fac[n], mod-2);
    for(int i = n;i;-- i) inv[i-1] = (LL)inv[i] * i % mod;
}
void qmo(int &x){x += x >> 31 & mod;}
void calrev(int len){
    int L = -1; lim = 1;
    while(lim <= len){lim <<= 1; ++ L;}
    for(int i = 0;i < lim;++ i)
        rev[i] = (rev[i>>1]>>1) | ((i&1)<<L);
    for(int i = 1;i < lim;i <<= 1){
        int wn = ksm(3, (mod-1)/(i<<1)); w[i] = 1;
        for(int j = 1;j < i;++ j) w[i+j] = (LL)w[i+j-1]*wn%mod;
    }
}
void NTT(int *A, bool op){
    for(int i = 0;i < lim;++ i)
        if(i < rev[i]) swap(A[i], A[rev[i]]);
    for(int md = 1;md < lim;md <<= 1)
        for(int i = 0;i < lim;i += md<<1)
            for(int j = 0;j < md;++ j){
                int y = (LL)A[md+i+j]*(op&&j?mod-w[(md<<1)-j]:w[md+j])%mod;
                qmo(A[md+i+j] = A[i+j] - y); qmo(A[i+j] += y-mod);
            }
    if(op){
        int inv = ksm(lim, mod-2);
        for(int i = 0;i < lim;++ i)
            A[i] = (LL)A[i] * inv % mod;
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> m; -- n; -- m;
    init(max(n,m<<1)+n); calrev(m<<1);
    for(int i = 0;i <= m;++ i) A[i] = B[i] = (LL)fac[2*i+n]*inv[i]%mod*inv[i]%mod*inv[n]%mod;
    for(int i = 0;i < m;++ i) C[i+1] = 2ll*fac[2*i]%mod*inv[i]%mod*inv[i+1]%mod;
    NTT(B, 0); NTT(C, 0);
    for(int i = 0;i < lim;++ i) B[i] = (LL)B[i] * C[i] % mod;
    NTT(B, 1);
    for(int i = 0;i <= m;++ i) qmo(A[i] -= B[i]);
    for(int i = 0;i <= m;++ i) ans = (ans + (LL)A[i]*A[m-i]) % mod;
    printf("%lld\n", (LL)ans*fac[2*n]%mod*inv[n]%mod*inv[n]%mod);
}

2021.9.3

ARC123F Insert Addition

對於序列 \(A=\{a_1,a_2,\cdots,a_n\}\),定義 \(f(A)=\{a_1,a_1+a_2,a_2,a_2+a_3,\cdots,a_{n-1}+a_n,a_n\}\)

給定三個正整數 \(a,b,n,l,r\),設 \(A=\{a,b\}\)\(B\)\(f^{(n)}(A)\) 去掉 \(>n\) 的數之后得到的序列,求 \(b_l,b_{l+1},\cdots,b_r\)

\(a,b\le n\le 3\cdot 10^5\)\(l\le r\le 10^{18}\)\(r-l\le 3\cdot 10^5\)


發現 \(a,b\) 的系數就是 Stern-Brocot 樹的前 \(n\) 層的中序遍歷。

實際上第 \(n\) 層之后的數就不可能 \(\le n\) 了,所以 \(B\) 就是所有 \(x\perp y\land ax+by\le n\)\((x,y)\)\(x/y\) 排序之后的結果。

注意到 \(f^{(n)}(A)=f^{(n-1)}(\{a,a+b\})-\{a+b\}+f^{(n-1)}(\{a+b,b\})\) 的遞歸性質,於是就可以直接在 SB 樹上二分,需要的就是求一棵子樹的大小,直接 Mobius 反演即可。

暴力實現的復雜度是大約是 2log,整除分塊套類歐是 \(\text{soft-}O(\sqrt n)\) 的。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 300003, M = 26000;
int a, b, n, tot, pri[M], mu[N];
bool notp[N]; LL l, r;
void print(int x){
    if(l > r) return; -- r;
    if(l == 1){printf("%d ", x); return;} -- l;
}
void print(int a, int b){if(a + b <= n){print(a, a + b); print(a + b); print(a + b, b);}}
LL calc(int a, int b){
    if(a < b) swap(a, b);
    LL res = 0;
    for(int i = 1, tmp;a+b <= (tmp = n/i);++ i) if(mu[i])
        for(int j = 1;a*j <= tmp;++ j)
            res += (tmp - a*j) / b * mu[i];
    return res;
}
void work(int a, int b){
    if(l > r || a + b > n) return;
    LL c = calc(a, b);
    if(l > c){l -= c; r -= c; return;}
    if(l == 1 && c <= r){print(a, b); return;}
    work(a, a + b); print(a + b); work(a + b, b);
}
int main(){
    ios::sync_with_stdio(false);
    cin >> a >> b >> n >> l >> r;
    mu[1] = 1; notp[0] = notp[1] = true;
    for(int i = 2;i <= n;++ i){
        if(!notp[i]){pri[tot++] = i; mu[i] = -1;}
        for(int j = 0;j < tot && i * pri[j] <= n;++ j){
            notp[i * pri[j]] = true;
            if(i % pri[j]) mu[i * pri[j]] = -mu[i];
            else break;
        }
    }
    print(a); work(a, b); print(b);
}

2021.9.5

上午開了場 vp,果然又因為沙雕錯誤卡題了 qwq

2021.9.6

G Colorful Doors

直線上有 \(n\) 對傳送門,進入一道傳送門時會從與之對應的傳送門的相同方向出來。

Snuke 從左邊向右一直走,給定 \(s_1,s_2,\cdots,s_{2n-1}\) 表示 Snuke 有沒有從第 \(i\) 道傳送門出來過。

構造放置這 \(n\) 對傳送門的方案,需判斷無解。

\(n\le 10^5\)


若將直線接成一個環,即認為第 \(2n\) 道傳送門的右邊是第 \(1\) 道傳送門,則這個環上的路徑等價於若干個環,條件即為欽定其中的一個環恰好是某些段。

先看只有一個環的情況:

  • \(n\) 是偶數,一個構造是 \((1,2,1,2,3,4,3,4,\cdots,2n-1,2n,2n-1,2n)\)
  • \(n\) 是奇數,發現玩不出來所以無解,因為考慮加一對傳送門,如果加在同一個環上那么這個環會裂開,如果加在兩個不同環上那么這兩個環會合並,所以環數與 \(n-1\) 同奇偶。

考慮兩邊都是 \(1\) 的門的個數 \(s\),因為它們之間要兩兩對應所以 \(s\) 必須是偶數。

  • \(s\)\(4\) 的倍數,可以通過調整 \(1\) 連續段開頭和末尾的傳送門使得恰好跳過 \(0\) 這些段,剩下的就變成 \(n\) 是偶數且全 \(1\) 的情況。
  • \(s\)\(4k+2\pod{k\in\mathbb N}\),若長度 \(>1\)\(1\) 連續段只有一個則相當於 \(n\) 是奇數且全 \(1\) 的情況,否則可以先將兩對門配對,規約到 \(s=4k\) 的情況。
#include<bits/stdc++.h>
#define PB emplace_back
using namespace std;
const int N = 200003;
char str[N];
int n, m, num, ans[N];
vector<int> v[2][2];
void work(vector<int> &v, int val){v.erase(lower_bound(v.begin(), v.end(), val));}
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> str+1; str[0] = str[n<<1] = '1';
    for(int i = 0;i < (n<<1);++ i)
        v[str[i] == '1'][str[i+1] == '1'].PB(i);
    m = v[1][1].size();
    if(m & 1){puts("No"); return 0;}
    if(m & 2){
        int a = 0;
        while(a <= n<<1 && str[a] == '1') ++ a;
        if(a > n<<1){puts("No"); return 0;}
        while(true){
            while(a < n<<1 && str[a+1] == '0') ++ a;
            if(a >= n<<1){puts("No"); return 0;}
            if(a+2 <= n<<1 && str[a+2] == '1') break;
            a += 2;
        }
        int b = a+1;
        while(b <= n<<1 && str[b] == '1') ++ b;
        if(b > n<<1){puts("No"); return 0;}
        int p = 0;
        while(p < m && v[1][1][p] > a && v[1][1][p] < b-1) ++ p;
        if(p >= m){puts("No"); return 0;}
        while(p+1 < m && v[1][1][p+1] < a) ++ p;
        int x = a, y = b-1, z = a+1, w = v[1][1][p];
        ans[x] = ans[y] = ++num;
        ans[z] = ans[w] = ++num;
        work(v[0][1], x); work(v[1][0], y);
        work(v[1][1], z); work(v[1][1], w);
    }
    for(int i = 0;i < v[0][1].size();++ i) ans[v[0][1][i]] = ans[v[1][0][i]] = ++num;
    for(int i = 0;i < v[0][0].size();i += 2) ans[v[0][0][i]] = ans[v[0][0][i+1]] = ++num;
    for(int i = 0;i < v[1][1].size();i += 4){
        ans[v[1][1][i]] = ans[v[1][1][i+2]] = ++num;
        ans[v[1][1][i+1]] = ans[v[1][1][i+3]] = ++num;
    }
    puts("Yes");
    for(int i = 0;i < n<<1;++ i) printf("%d ", ans[i]);
}

2021.9.7

SWERC 2020

L Restaurants

給定 \(n\) 個顧客和 \(m\) 個餐廳,每個顧客預訂了一些餐廳,並對這些餐廳進行排序,每個餐廳對預訂它的顧客進行排序,第 \(i\) 個餐廳的容量是 \(c_i\),構造給一些顧客分配餐廳的方案,使得:

  • 每個餐廳都不容納多於容量數量的顧客;
  • 每個顧客都只分配到一家餐廳;
  • 沒有一對餐廳 \(r\) 與顧客 \(c\),使得:
    • 顧客 \(c\) 預訂了餐廳 \(r\)
    • 顧客 \(c\) 沒有分配到餐廳 或 相比於他被分配到的餐廳,他更喜歡餐廳 \(r\)
    • 餐廳 \(r\) 有一些剩余位置 或 已滿但相比於至少一個分配到餐廳 \(r\) 的顧客,它更喜歡顧客 \(c\)

\(c_i\le n\le 5\cdot 10^4\)\(m\le 10^4\),預訂數量 \(\le 10^6\)


翻譯題面好 tm 累,感覺直接上穩定婚姻匹配模板就行,結果沖一發就過了

#include<bits/stdc++.h>
#define PB emplace_back
using namespace std;
typedef pair<int, int> pii;
const int N = 50003, M = 10003, K = 2000003;
int n, m, c[M], q[K], fr, re;
string str;
stringstream ss;
vector<int> V[N];
map<int, int> mp[M];
set<pii> S[M];
bool del[N];
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for(int i = 0;i < m;++ i) cin >> c[i];
    getline(cin, str);
    for(int i = 0, u;i < n;++ i){
        q[re++] = i;
        getline(cin, str); ss.clear(); ss << str;
        while(ss >> u) V[i].PB(u-1);
        reverse(V[i].begin(), V[i].end());
    }
    for(int i = 0, u;i < m;++ i){
        getline(cin, str); ss.clear(); ss << str;
        int now = 0;
        while(ss >> u){mp[i][u-1] = -now; ++now;}
    }
    while(fr < re){
        int u = q[fr++];
        if(V[u].empty()){del[u] = true; continue;}
        int v = V[u].back(); V[u].pop_back();
        S[v].emplace(mp[v][u], u);
        if(S[v].size() > c[v]){
            q[re++] = S[v].begin()->second;
            S[v].erase(S[v].begin());
        }
    }
    for(int i = 0;i < n;++ i) if(!del[i]) printf("%d\n", i+1);
}

M Fantasmagorie

定義 \(01\) 矩陣是好的當且僅當:

  • 邊界上的元素都相同;
  • 不含 \(\begin{pmatrix}0 & 1 \\ 1 & 0\end{pmatrix}\)\(\begin{pmatrix}1 & 0 \\ 0 & 1\end{pmatrix}\) 的連續子矩陣。
  • 將同色四連通塊縮點之后,每個點的度數 \(\le 2\)

給定兩個 \(H\times W\) 的好矩陣,構造一組每次 flip 一個元素,從第一個矩陣變為第二個矩陣的長度 \(\le 2.5\cdot 10^5\) 的操作序列,使得兩種顏色的四連通塊個數保持不變,且自始至終是好矩陣。需判斷無解。

\(H\le 64\)\(W\le 103\)


還是這個條件太惡心了,vp 的時候看都不想看

條件說明同色四連通塊應當是套在一起的。

因為操作可逆,所以考慮將兩個矩陣都變為一個標准化的矩陣。

.........    .........    .........
..#####..    .#######.    .#######.
..##.##..    .###.###.    .#.....#.
..#...#..    .##...##.    .#.###.#.
..#.#.#..    .##.#.##.    .#.###.#.
..#...#.. => .##...##. => .#.###.#.
..#####..    .#######.    .#.###.#.
...#.#...    .#######.    .#.###.#.
...#.#...    .#######.    .#.###.#.
...#.#...    .#######.    .#.....#.
.###.###.    .#######.    .#######.
.........    .........    .........

我們可以把次外的連通塊擴展,將最外的連通塊擠成寬為 \(1\) 的環,然后去掉邊界之后就是一個子問題。只需要對最外的連通塊 bfs,按照與左上角的距離從大到小 flip 就可以保證自始至終是好矩陣。

由此可得有解當且僅當最終得到的矩陣相同,也即最外層字符及連通塊數均相同。

#include<bits/stdc++.h>
#define PB emplace_back
#define fi first
#define se second
#define ALL(x) x.begin(), x.end()
using namespace std;
typedef pair<int, int> pii;
typedef tuple<int, int, int> tiii;
const int N = 111, d[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
int m, n;
struct {
    int lys, dis[N][N];
    string S[N];
    vector<pii> ans;
    void work(){
        for(int i = 0;i < n;++ i) cin >> S[i];
        lys = 0;
        while(true){
            queue<pii> q; priority_queue<tiii> pq;
            memset(dis, -1, sizeof dis);
            q.emplace(lys, lys); dis[lys][lys] = 0;
            while(!q.empty()){
                pii _ = q.front(); q.pop();
                for(int i = 0;i < 4;++ i){
                    int nx = _.fi + d[i][0], ny = _.se + d[i][1];
                    if(nx >= 0 && nx < n && ny >= 0 && ny < m && S[lys][lys] == S[nx][ny] && !~dis[nx][ny]){
                        q.emplace(nx, ny); dis[nx][ny] = dis[_.fi][_.se] + 1;
                    }
                }
            }
            for(int i = lys;i < n-lys;++ i)
                for(int j = lys;j < m-lys;++ j)
                    if(S[lys][lys] != S[i][j]) pq.emplace(1e9, i, j);
            ++ lys; if(pq.empty()) break;
            while(!pq.empty()){
                int x = get<1>(pq.top()), y = get<2>(pq.top()); pq.pop();
                if(S[lys-1][lys-1] == S[x][y]){ans.PB(x, y); S[x][y] ^= '.'^'#';}
                for(int i = 0;i < 4;++ i){
                    int nx = x + d[i][0], ny = y + d[i][1];
                    if(nx >= lys && nx < n-lys && ny >= lys && ny < m-lys && S[lys-1][lys-1] == S[nx][ny] && ~dis[nx][ny]){
                        pq.emplace(dis[nx][ny], nx, ny); dis[nx][ny] = -1;
                    }
                }
            }
        }
    }
} _1, _2;
int main(){
    ios::sync_with_stdio(false);
    cin >> m >> n;
    _1.work(); _2.work();
    if(_1.S[0][0] != _2.S[0][0] || _1.lys != _2.lys){
        puts("IMPOSSIBLE"); return 0;
    }
    reverse(ALL(_2.ans));
    for(const pii &o : _1.ans) printf("%d %d\n", o.se, o.fi);
    for(const pii &o : _2.ans) printf("%d %d\n", o.se, o.fi);
}

J Daisy's Mazes

給定 \(N\) 個點 \(M\) 條邊的有向圖,每條邊染 \(C\) 種顏色,Daisy 帶上一個棧從 \(0\) 號點出發,棧的每個元素是 \(C\) 種顏色之一。

設當前在點 \(u\),棧頂元素是 \(c\),若 \(u\) 有顏色 \(c\) 的出邊則選擇一條顏色 \(c\) 的出邊並彈棧,否則任意選擇一條出邊,將該種顏色壓入棧。

已知 Daisy 可以走到 \(N-1\) 號點,求初始時的棧大小的最小值。

\(N\le 50\)\(M\le 100\)\(C\le 20\),保證有解。


對於這種圖上路徑的括號匹配問題,考慮從剝括號的過程設計 dp 狀態。

考慮再引入 \(n\) 個點 \(0=x_0,x_1,\cdots,x_{n-1}\)\(x_{i+1}\)\(x_i\) 連所有顏色的邊,則棧大小可以為 \(m\) 當且僅當帶空棧從 \(x_m\) 出發可以走到終點。

因為初始的棧如果有兩個連續的相同顏色則一定不優(要用的時候可以轉化為先壓入再彈出),所以可以先從 \(x_m\) 走到 \(0\) 得到任意的大小為 \(m\) 的棧。

然后 \(N-1\) 向自己連所有顏色的邊,就可以限制初始和結束時刻都是空棧,經過的邊的顏色即為括號序列。

於是就可以 dp 了,設 \(f(c,r_1,r_2)\) 表示不匹配棧頂的顏色 \(c\) 的情況下,是否有 \(r_1\to r_2\) 的括號序列。

轉移即為拼接括號序列和套一層括號。時間復雜度是玄學

#include<bits/stdc++.h>
#define PB emplace_back
using namespace std;
const int N = 103, C = 21;
int n, m, k;
bool f[N][N][C];
vector<int> E[N][C], R[N][C];
void add(int u, int v, int c){E[u][c].PB(v); R[v][c].PB(u);}
void dfs(int u, int v, int c){
    if(f[u][v][c]) return;
    f[u][v][c] = true;
    for(int i = 1;i < 2*n;++ i){
        if(f[v][i][c]) dfs(u, i, c);
        if(f[i][u][c]) dfs(i, v, c);
    }
    for(int x : R[u][c])
        for(int d = 0;d <= k;++ d)
            if(x < n || E[x][d].empty())
                for(int y : E[v][c])
                    dfs(x, y, d);
}
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> m >> k;
    for(int i = 0, u, v, c;i < m;++ i){
        cin >> u >> v >> c; add(u + n, v + n, c);
    }
    for(int i = 0;i < k;++ i) add(2*n-1, 2*n-1, i);
    for(int i = 1;i < n;++ i)
        for(int j = 0;j < k;++ j)
            add(i, i + 1, j);
    for(int i = 0;i < 2*n;++ i)
        for(int j = 0;j <= k;++ j)
            dfs(i, i, j);
    for(int i = n;i;-- i)
        if(f[i][2*n-1][k]){printf("%d\n", n - i); return 0;}
}

假裝在做集訓隊作業,進度 13/150


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM