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);
    }
}

K Welcome to ICPCCamp 2017

给定 \(m\) 支队伍打 \(n\) 场区域赛和一场 EC-Final。第 \(i\) 场区域赛有 \(k_i\) 支队伍打,排名是 \(r_{i,1},\cdots,r_{i,k_i}\)。所有队伍都打了 EC-Final,排名是 \(r_{n+1,1},\cdots,r_{n+1,m}\)

你任意选取正整数 \(x,y\) 和长为 \(n\) 的排列 \(p\),求 \((r_{n+1,1},\cdots,r_{n+1,y},r_{p_1,1},\cdots,r_{p_n,1},r_{p_1,2},\cdots,r_{p_n,2},\cdots)\) 的前 \(x+y\) 支不同队伍的集合的个数\(\bmod(10^9+7)\)

\(\sum k_i,\sum m\le 2\cdot 10^5\)


这就是叉姐说的聊天题吗

从大到小枚举 EC-Final 的最高排名的未出线队伍 \(i\),不妨设其前面的队伍都通过 EC-Final 名额出线,考虑其后面有队伍通过区域赛出线的情况,只需要让队伍 \(i\) 拿不到区域赛出线名额即可,需要单点修改前缀和查询,用树状数组维护即可。

#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <vector>

const int MOD = (int)1e9 + 7;

void update(int& x, int a)
{
    x += a;
    if (x >= MOD) {
        x -= MOD;
    }
}

int main()
{
    int n, m;
    while (scanf("%d%d", &n, &m) == 2) {
        std::vector<std::vector<int>> regionals;
        for (int i = 0; i < n; ++ i) {
            int k;
            scanf("%d", &k);
            std::vector<int> regional(k);
            for (int j = 0; j < k; ++ j) {
                scanf("%d", &regional.at(j));
            }
            regionals.push_back(regional);
        }
        std::vector<int> ec(m), rank(m, m);
        for (int i = 0; i < m; ++ i) {
            int team;
            scanf("%d", &team);
            team --;
            ec.at(team) = i;
        }
        for (auto&& regional : regionals) {
            for (int i = 0; i < static_cast<int>(regional.size()); ++ i) {
                int team = regional.at(i);
                team --;
                auto& ref = rank.at(ec.at(team));
                ref = std::min(ref, i);
            }
        }
        int result = m + 1; // y == m
        std::vector<int> power(m + 1, 1);
        std::vector<int> count(m + 1);
        for (int y = m - 1; y >= 0; -- y) {
            auto&& r = rank.at(y);
            for (int i = r; i >= 0; i -= ~i & i + 1) {
                update(result, count.at(i));
            }
            if (rank.at(y) < m) {
                for (int i = r; i <= m; i += ~i & i + 1) {
                    update(count.at(i), power.at(r));
                }
                update(power.at(r), power.at(r));
            }
        }
        printf("%d\n", result);
    }
}

不太想写了放个 std 在这里

A Even Three is Odd

给定 \(w_1,w_2,\dots,w_n\),求

\[\sum_{x\in[n]^n}\prod_{i=1}^{n-2}w_{\max(x_i,x_{i+1},x_{i+2})}\bmod(10^9+7) \]

\(\sum n\le 2000\)


神仙 dp

不能把两个数记录进状态里,就考虑只记录最大值,设 \(f_{i,j,k}\) 表示考虑 \(x\) 的前 \(i+j\) 个值,\(x_i,x_{i+1},x_{i+2}\) 的最靠后的最大值是 \(x_{i+j}=k\) 的情况下,\(\prod_{l=1}^iw_{\max(x_l,x_{l+1},x_{l+2})}\) 之和。考虑什么情况下 \(f_{i,j,k}\) 能转移到 \(f_{i+1,j',k'}\),枚举一下发现是 \(j'=j-1\land k'=k\),或者 \(j=0\land k>k'\),或者 \(j'=2\land k\le k'\),使用前/后缀和优化,时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2003, mod = 1e9+7;
int n, w[N], f[3][N], g[3][N];
void qmo(int &x){x += x >> 31 & mod;}
int main(){
    ios::sync_with_stdio(false);
    while(cin >> n){
        for(int i = 0;i < n;++ i){
            cin >> w[i]; f[0][i] = w[i];
            f[1][i] = (i+1ll) * w[i] % mod;
            f[2][i] = (i+1ll) * f[1][i] % mod;
        }
        for(int i = 3;i < n;++ i){
            memset(g, 0, sizeof g); g[2][0] = f[2][0];
            for(int j = 1;j < n;++ j)
                g[2][j] = (g[2][j-1] + (LL)j*j*f[0][j] + (LL)j*f[1][j] + f[2][j]) % mod;
            int tmp = 0;
            for(int j = n-1;~j;-- j){
                qmo(g[0][j] = tmp + f[1][j] - mod);
                g[1][j] = (tmp*(j+1ll) + f[2][j]) % mod;
                g[2][j] = (tmp*(j+1ll)*(j+1ll) + g[2][j]) % mod;
                for(int k = 0;k < 3;++ k) g[k][j] = (LL)g[k][j] * w[j] % mod;
                qmo(tmp += f[0][j] - mod);
            }
            memcpy(f, g, sizeof f);
        }
        int ans = 0;
        for(int i = 0;i < n;++ i)
            ans = (ans + (LL)i*i*f[0][i] + (LL)i*f[1][i] + f[2][i]) % mod;
        printf("%d\n", ans);
    }
}
posted @ 2021-09-10 11:48  mizu164  阅读(317)  评论(0编辑  收藏  举报