1. 程式人生 > 其它 >Petrozavodsk Winter-2017. Xiaoxu Guo Contest 5【雜題】

Petrozavodsk Winter-2017. Xiaoxu Guo Contest 5【雜題】

G Matrix Recurrence

給定 \(M_0,B\in\Z_{\text{mod}}^{m\times m}\)\(c_1,\cdots,c_n\in\N\),定義

\[M_i=\left(\prod_{j=c_i}^{i-1}M_j\right)\times B \]

\(M_n\)\(n\le 10^6\)\(m\le 5\)\(2\le\text{mod}\le 10^9\)\(c_i<i\)\(c_1\le c_2\le\cdots\le c_n\)\(\text{TL}=10\text s\)


“バカ-trick”(兩個棧模擬佇列)的模板題。時間複雜度 \(O(nm^3)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1000003;
int n, m, mod;
struct Mat {
    int x[5][5];
    Mat(){memset(x, 0, sizeof x);}
    void reset(){
        for(int i = 0;i < 5;++ i)
            for(int j = 0;j < 5;++ j)
                x[i][j] = i == j;
    }
    Mat operator = (const Mat &o){memcpy(x, o.x, sizeof x); return *this;}
    Mat operator * (const Mat &o) const {
        Mat res;
        for(int i = 0;i < m;++ i)
            for(int j = 0;j < m;++ j){
                LL tmp = 0;
                for(int k = 0;k < m;++ k) tmp += (LL)x[i][k] * o.x[k][j];
                res.x[i][j] = tmp % mod;
            }
        return res;
    }
} A[N], B, now;
void solve(){
    for(int i = 0;i < m;++ i)
        for(int j = 0;j < m;++ j)
            scanf("%d", &A[0].x[i][j]);
    for(int i = 0;i < m;++ i)
        for(int j = 0;j < m;++ j)
            scanf("%d", &B.x[i][j]);
    int p = 0; now.reset();
    for(int i = 1, c;i <= n;++ i){
        scanf("%d", &c);
        if(c > p){
            for(int j = i-2;j >= c;-- j)
                A[j] = A[j] * A[j+1];
            p = i-1; now.reset();
        }
        A[i] = A[c] * now * B;
        now = now * A[i];
    }
    for(int i = 0;i < m;++ i)
        for(int j = 0;j < m;++ j)
            printf("%d%c", A[n].x[i][j], " \n"[j==m-1]);
}
int main(){while(~scanf("%d%d%d", &n, &m, &mod)) solve();}

F Multi-stage Marathon

給定 \(n\) 個點的有向圖,\(m\) 個人從時刻 \(t_i\) 開始從點 \(v_i\) 隨機遊走,設 \(E_t\) 表示 \(n\) 號點在時刻 \(t\) 的期望人數模 \(10^9+7\),求 \(\bigoplus_{t=1}^TE_t\)

\(n\le 70\)\(m\le 10^4\)\(T\le 2\cdot 10^6\)


矩陣乘法模板題,注意到矩陣乘向量的複雜度是 \(O(n^2)\),而求矩陣乘向量的某一維的複雜度是 \(O(n)\)

平衡一下,預處理 \(G^0,G^1,\cdots,G^L\),其中 \(G\) 是轉移矩陣。就可以做到 \(O(n^2\lceil\frac{T}{L}\rceil)\)

轉移一段,\(O(n)\) 算一項答案。

時間複雜度 \(O(n^3L+nT+n^2(\frac TL+m))\),大概取 \(L\approx100\) 即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 10003, LEN = 125, mod = 1e9+7;
int n, m, T, t[N], v[N], inv[75], ans;
char str[75];
void qmo(int &x){x += x >> 31 & mod;}
struct Mat {
    int x[70][70];
    Mat(){memset(x, 0, sizeof x);}
    Mat operator = (const Mat &o){memcpy(x, o.x, sizeof x); return *this;}
    void reset(){
        for(int i = 0;i < n;++ i)
            for(int j = 0;j < n;++ j)
                x[i][j] = i == j;
    }
    Mat operator * (const Mat &o) const {
        Mat res;
        for(int i = 0;i < n;++ i)
            for(int k = 0;k < n;++ k)
                for(int j = 0;j < n;++ j)
                    res.x[i][j] = (res.x[i][j] + (LL)x[i][k] * o.x[k][j]) % mod;
        return res;
    }
} A[LEN+1];
struct Vec {
    int x[70];
    Vec(){memset(x, 0, sizeof x);}
    Vec operator * (const Mat &o) const {
        Vec res;
        for(int i = 0;i < n;++ i)
            for(int j = 0;j < n;++ j)
                res.x[j] = (res.x[j] + (LL)x[i] * o.x[i][j]) % mod;
        return res;
    }
    int operator ^ (const Mat &o) const {
        int res = 0;
        for(int i = 0;i < n;++ i)
            res = (res + (LL)x[i] * o.x[i][n-1]) % mod;
        return res;
    }
} now;
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> m >> T; A[0].reset(); inv[1] = 1;
    for(int i = 2;i <= n;++ i) inv[i] = mod - (LL)mod / i * inv[mod % i] % mod;
    for(int i = 0;i < n;++ i){
        cin >> str; int cnt = 0;
        for(int j = 0;j < n;++ j) cnt += str[j] - '0';
        for(int j = 0;j < n;++ j) A[1].x[i][j] = str[j] != '0' ? inv[cnt] : 0;
    }
    for(int i = 2;i <= LEN;++ i) A[i] = A[i-1] * A[1];
    for(int i = 0;i < m;++ i){cin >> t[i] >> v[i]; -- v[i];}
    t[m] = T;
    for(int i = 0;i < m;++ i){
        ++now.x[v[i]];
        int step = t[i+1] - t[i];
        for(;step >= LEN;step -= LEN){
            for(int j = 0;j < LEN;++ j) ans ^= now ^ A[j];
            now = now * A[LEN];
        }
        for(int j = 0;j < step;++ j) ans ^= now ^ A[j];
        now = now * A[step];
    }
    printf("%d\n", ans ^ now.x[n-1]);
}

H Permutation and noitatumreP

【題目描述略】

寫個暴力扔到 OEIS 裡,發現了線性遞推式,於是就做完了

看上去不太有社論,只有 std,所以給我整不會了。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9+7, P[] = {0, 3, mod-2, 1, mod-1}, R[] = {2, 6, 16, 36, 80};
int n, f[30][5], g[5];
void mul(const int *a, const int *b, int *c){
    static int tmp[9];
    memset(tmp, 0, sizeof tmp);
    for(int i = 0;i < 5;++ i)
        for(int j = 0;j < 5;++ j)
            tmp[i+j] = (tmp[i+j] + (LL)a[i] * b[j]) % mod;
    for(int i = 8;i > 4;-- i){
        for(int j = 1;j < 5;++ j)
            tmp[i-j] = (tmp[i-j] + (LL)P[j] * tmp[i]) % mod;
        tmp[i] = 0;
    }
    memcpy(c, tmp, 20);
}
void solve(){
    if(n == 1){puts("1"); return;} n -= 2;
    memset(g, 0, sizeof g); g[0] = 1;
    for(int i = 29;~i;-- i) if(n>>i&1) mul(g, f[i], g);
    int ans = 0;
    for(int i = 0;i < 5;++ i) ans = (ans + (LL)R[i] * g[i]) % mod;
    printf("%d\n", ans);
}
int main(){
    ios::sync_with_stdio(false);
    f[0][1] = 1;
    for(int i = 1;i < 30;++ i) mul(f[i-1], f[i-1], f[i]);
    while(cin >> n) solve();
}

C City United

給定 \(n\) 個點的無向圖,求連通匯出子圖個數\(\bmod 2\)

\(n\le 50\),邊的兩端點編號之差 \(\le 13\)


可以轉化為對連通塊黑白染色的方案數\(\bmod 4\)。即對所有點染黑白灰三色,使得黑色點與白色點之間沒有連邊。直接 dp 即可,時間複雜度 \(O(n3^k)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 50, M = 1594323;
int n, m, k, G[N], pw[14], pre[M][2];
char f[M], g[M];
void upd(char &a, const char &b){a = a + b & 3;}
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 >> m;
    for(int i = 0, u, v;i < m;++ i){
        cin >> u >> v;
        if(u > v) swap(u, v);
        chmax(k, v-u); -- v;
        G[v] |= 1 << v-u;
    } pw[0] = 1;
    for(int i = 1;i <= k;++ i) pw[i] = 3 * pw[i-1];
    for(int i = 1;i < pw[k];++ i)
        for(int j = 0;j < 2;++ j)
            pre[i][j] = pre[i/3][j] << 1 | (i%3 == j+1);
    f[0] = 1;
    for(int i = 0;i < n;++ i){
        memset(g, 0, sizeof g);
        for(int S = 0;S < pw[k];++ S) if(f[S]){
            int T = S % pw[k-1] * 3;upd(g[T], f[S]);
            if(!(G[i] & pre[S][1])) upd(g[T+1], f[S]);
            if(!(G[i] & pre[S][0])) upd(g[T+2], f[S]);
        }
        memcpy(f, g, sizeof f);
    }
    char res = 3;
    for(int i = 0;i < pw[k];++ i) upd(res, f[i]);
    putchar(res >> 1 | '0');
}

D Coins 2

給定面值為 \(1,2,\cdots,n\) 的硬幣分別 \(a_1,a_2,\cdots,a_n\) 個,求能組合出的錢數。

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


\(m=\text{lcm}(1,2,\cdots,n)\),若 \(x\ge nm\) 能夠拼成,則必有一種面值 \(i\) 的個數 \(\ge\frac mi\),得到 \(x-m\) 也可以被拼成。

根據對稱性,設 \(s=\sum ia_i\),若 \(x\le s-nm\),則 \(x+m\) 也能拼成。

所以 \(\forall x\in[nm,s-(n+1)m]\)\(x\) 能拼成當且僅當 \(x+m\) 能拼成。所以只需要算出 \([1,(n+1)m]\) 能否被拼成即可。

時間複雜度 \(O(n^2m)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5765760;
int n, m, L, a[16], f[N]; LL sum, half, ans;
int lcm(int x, int y){return x / __gcd(x, y) * y;}
template<typename T>
bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int main(){
    ios::sync_with_stdio(false);
    while(cin >> n){
        sum = ans = 0; m = 1;
        for(int i = 1;i <= n;++ i){
            cin >> a[i]; m = lcm(m, i);
            sum += (LL)a[i] * i;
        }
        L = m * n; half = sum >> 1;
        memset(f, 0x3f, L + m << 2); f[0] = 0;
        for(int i = 1;i <= n;++ i)
            for(int j = 0;j < L + m;++ j){
                f[j] = f[j] <= a[i-1] ? 0 : 1e9;
                if(j >= i && f[j-i] < a[i])
                    chmin(f[j], f[j-i] + 1);
            }
        for(int i = 0;i < L + m;++ i) f[i] = f[i] <= a[n];
        for(int i = 0;i < L && i <= half;++ i) ans += f[i];
        for(int i = L;i < L + m && i <= half;++ i)
            if(f[i]) ans += (half - i) / m + 1;
        ans <<= 1;
        if(!(sum & 1) && f[half < L ? half : half % m + L]) -- ans;
        printf("%lld\n", ans);
    }
}