「EXOI - Test 3」晨辉 题解

「EXOI - Test 3」晨辉


英雄他就要出发
去远方寻找回答
他的步伐多么坚定
梦想多么伟大
也许路途很遥远
但一定能够实现

高高山上的风
随英雄远去
行过长路
踏过荆棘

冉冉升起的太阳
把路途照亮
为他加冕
伴他身旁

高高山上的风
随英雄远去
行过长路
踏过荆棘

冉冉升起的太阳
把路途照亮
为他加冕
伴他身旁

比赛标题是 方舟中 塞西莉亚 唱的歌谣的名字,前三题的背景也是这首歌的歌词。

最后一题的背景是 吾导先路 剧情末尾中的一段话。(没办法塞西莉亚的歌谣太短了

塞西莉亚真的好可爱!

四道题目的正解是题目名称的倒序。

A「EXOI」简单动态规划题

好像可以小范围打表然后直接冲 OEIS

其实你手模出前几行可以发现,答案的个数和 \(n\)\(1\) 的个数有关,若有 \(x\)\(1\),那么答案便是 \(2^x\)

/*
Work by: Suzt_ilymtics
*/
#include<bits/stdc++.h>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e5+5;
const int INF = 1e18;
const int mod = 1e9+7;

int n;

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
    return f ? -s : s;
}

signed main()
{
    int T = read();
    while(T--) {
        n = read();
        assert(n <= INF);
        assert(n >= 0);
        if(!n) { puts("1"); continue; }
        int cnt = 0;
        assert((1ll << 60) >= n);
        for(int i = 0; i <= 60; ++i) if(n & (1ll << i)) cnt ++;
         assert((1ll << cnt) <= n+1);
        printf("%lld\n", (1ll << cnt));
    }
    return 0;
}

B「EXOI」简单数据结构题

来自牛客的某道题

因此对于任意两个颜色相同的点 \(x,y\),两个点的最短路径上的所有点的颜色必须和他们相同。

这句话转化一下就是一种颜色只会在同一个连通块内。

然后你可以枚举用 \(x\) 种颜色,那么需要将原来那棵树划分成 \(x\) 个连通块。

也就是说需要在 \(n-1\) 条边中选择 \(x-1\) 条边断开。

再加上 \(x\) 种颜色的排列数,用 \(x\) 种颜色带来的贡献,答案就是

\[ans = \sum_{i=1}^{\min\{n,k\}} \binom{k}{i} \binom{n-1}{i-1} i! i^a \]

看上去和树的结构根本没关系是吧

/*
Work by: Suzt_ilymtics
*/
#include<bits/stdc++.h>
#define LL long long
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 3e5+5;
const int INF = 1e9+7;
const int mod = 998244353;

int n, a, k, res = 0;
int fac[MAXN], inv[MAXN];

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
    return f ? -s : s;
}

void Init(int M) {
    fac[0] = fac[1] = inv[0] = inv[1] = 1;
    for(int i = 2; i <= M; ++i) {
        fac[i] = fac[i - 1] * i % mod;
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
    }
    for(int i = 2; i <= M; ++i) inv[i] = inv[i] * inv[i - 1] % mod;
}

int C(int n, int m) { return n < m ? 0 : fac[n] * inv[m] % mod * inv[n - m] % mod; }

int Pow(int x, int p) {
    int res = 1;
    while(p) {
        if(p & 1) res = res * x % mod;
        x = x * x % mod, p >>= 1;
    }
    return res;
}

signed main()
{
    n = read(), a = read(), k = read();
    Init(max(n, k));
    for(int i = 1; i <= min(n, k); ++i) {
        (res += C(k, i) * C(n - 1, i - 1) % mod * fac[i] % mod * Pow(i, a) % mod) %= mod;
    }
    printf("%lld\n", res);
    return 0;
}

C「EXOI」简单计数题

讲个笑话,这是 sdwc day1 的 T2

码农题。直接上线段树维护矩阵,然后加法改为矩阵乘法,区间加 \(x\),相当于区间乘一个矩阵 \(base^x\)

因为这个玩意满足分配律,所以把相邻元素直接加起来没有问题。

\(base\) 矩阵需要设为

\[\begin{bmatrix} X & 1 \\ Y & 0 \end{bmatrix} \]

时间复杂度 \(\mathcal O(2^3 m \log n \log V)\)

#include<bits/stdc++.h>
#define int long long
#define orz cout << "lkp AK IOI!\n"
using namespace std;
const int MAXN = 4e5 + 10;
const int mod = 1004535809;

int n, m;
int a[MAXN];

int read() {
	int s = 0, f = 0;
	char ch = getchar();
	while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
	while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
	return f ? -s : s;
}

struct Matrix {
    int a[2][2];
    Matrix () { memset(a, false, sizeof a); }
    Matrix operator + (const Matrix b) const {
        Matrix res;
        for(int i = 0; i < 2; ++i) {
            for(int j = 0; j < 2; ++j) {
                res.a[i][j] = a[i][j] + b.a[i][j];
                if(res.a[i][j] > mod) res.a[i][j] -= mod;
            }
        }
        return res;
    }
    Matrix operator * (const Matrix b) const {
        Matrix res;
        for(int i = 0; i < 2; ++i) {
            for(int j = 0; j < 2; ++j) {
                for(int k = 0; k < 2; ++k) {
                    res.a[i][j] = (res.a[i][j] + this->a[i][k] * b.a[k][j] % mod) % mod;
                }
            }
        }
        return res;
    }
    void Print() {
        for(int i = 0; i < 2; ++i) {
            for(int j = 0; j < 2; ++j) {
                cout << a[i][j] << " ";
            }
            puts("");
        }
    }
}base, ans;

Matrix Init(int k, int type) {
    Matrix res, tmp = base;
    if(type) res.a[0][0] = 1, k --;
    else res.a[0][0] = res.a[1][1] = 1;
    while(k) {
        if(k & 1) res = res * tmp;
        tmp = tmp * tmp, k >>= 1;
    }
    return res;
}

namespace Seg {
    #define lson i << 1
    #define rson i << 1 | 1
    Matrix sum[MAXN << 2], lazy[MAXN << 2];
    void Push_up(int i) { sum[i] = sum[lson] + sum[rson]; }
    bool Check(int i) { return lazy[i].a[0][0] == 1 && lazy[i].a[1][1] == 1; }
    void Clear(int i) { lazy[i].a[0][0] = lazy[i].a[1][1] = 1, lazy[i].a[0][1] = lazy[i].a[1][0] = 0; }
    void Build(int i, int l, int r) {
        Clear(i);
        if(l == r) {
            sum[i] = Init(a[l], 1);
            return ;
        }
        int mid = (l + r) >> 1;
        Build(lson, l, mid), Build(rson, mid + 1, r);
        Push_up(i);
    }
    void Push_down(int i) {
        if(Check(i)) return ;
        sum[lson] = sum[lson] * lazy[i];
        sum[rson] = sum[rson] * lazy[i];
        lazy[lson] = lazy[lson] * lazy[i];
        lazy[rson] = lazy[rson] * lazy[i];
        Clear(i); 
    }
    void Modify(int i, int l, int r, int L, int R, Matrix c) {
        if(L <= l && r <= R) {
            sum[i] = sum[i] * c, lazy[i] = lazy[i] * c;
            return ;
        }
        Push_down(i);
        int mid = (l + r) >> 1;
        if(mid >= L) Modify(lson, l, mid, L, R, c);
        if(mid < R) Modify(rson, mid + 1, r, L, R, c);
        Push_up(i);
    }
    Matrix Query(int i, int l, int r, int L, int R) {
        if(L <= l && r <= R) return sum[i];
        Push_down(i);
        int mid = (l + r) >> 1; Matrix ans;
        if(mid >= L) ans = ans + Query(lson, l, mid, L, R);
        if(mid < R) ans = ans + Query(rson, mid + 1, r, L, R);
        return ans;
    }
}

signed main() {
    int X, Y;
	n = read(), m = read(), X = read(), Y = read();
	base.a[0][0] = X, base.a[0][1] = 1;
	base.a[1][0] = Y, base.a[1][1] = 0;
	for(int i = 1; i <= n; ++i) a[i] = read();
	Seg::Build(1, 1, n);
	for(int i = 1, opt, l, r, x; i <= m; ++i) {
	    opt = read();
        if(opt == 1) {
            l = read(), r = read(), x = read();
    	    Matrix tmp = Init(x, 0);
    	    Seg::Modify(1, 1, n, l, r, tmp);
        } else {
            l = read(), r = read();
            Matrix ans = Seg::Query(1, 1, n, l, r);
            printf("%lld\n", ans.a[0][0]);
        }
    }
    return 0;
}

D「EXOI」简单签到题

来自一道 CF 题。

一开始把 \(1\) 设为根,然后跑树形 DP。

\(f_{u,i}\) 表示以 \(u\) 为根的子树内有多少点到 \(u\) 的距离模 \(k\) 意义下为 \(i\)

转移式是

\[f_{u,i} = \sum f_{v,i-1} \]

当由 \(f_{v,0}\) 转移到 \(f_{u,1}\) 时,那么答案就会增加 \(f_{v,0}\)

这样做的复杂度是 \(\mathcal O(n^2)\) 的。

考虑换根 DP。

考虑从 \(u \to v\)

需要考虑更新 \(f_v\),因为 \(v\) 中子树的值是不需要变的,只需要考虑从 \(u\) 来的点对 \(v\) 的贡献。

用一个 \(g\) 暂时存一下之前 \(v\) 所在根的状态,

那么到 \(u\) 的距离为 \(i\) 的点可以用 \(f_{u,i} - g_{v,i-1}\) 来表示。

整个转移式为

\[f_{v,i} += (f_{u,i-1} - g_{v,i-2}) \]

答案 \(ans_v\) 的转移也比较好得到,就是

\[ans_v = ans_u - g_{v,0} + f_{u,0} - g_{v,k-1} \]

然后做完了,总复杂度为 \(\mathcal O(n)\)

/*
Work by: Suzt_ilymtics
*/
#include<bits/stdc++.h>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 2e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

struct edge { int to, nxt; }e[MAXN << 1];
int head[MAXN], num_edge = 1;

int n, k;
int siz[MAXN];
int f[MAXN][16], tmp[MAXN][16];
int ans[MAXN], sum = 0;

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
    return f ? -s : s;
}

void add_edge(int from, int to) { e[++num_edge] = (edge){to, head[from]}, head[from] = num_edge; }

void dfs(int u, int fa) {
    f[u][0] = 1, siz[u] = 1;
    for(int i = head[u]; i; i = e[i].nxt) {
        int v = e[i].to;
        if(v == fa) continue;
        dfs(v, u);
        siz[u] += siz[v];
        for(int j = 0; j < k; ++j) f[u][(j + 1) % k] += f[v][j];
        ans[1] += f[v][0];
    }
}

void dfs2(int u, int fa) {
    for(int i = head[u]; i; i = e[i].nxt) {
        int v = e[i].to;
        if(v == fa) continue;
        for(int j = 0; j < k; ++j) tmp[v][j] = f[v][j];
        for(int j = 0; j < k; ++j) {
            f[v][(j + 1) % k] += f[u][j] - tmp[v][(j - 1 + k) % k];
        }
        ans[v] = ans[u] - tmp[v][0] + f[u][0] - tmp[v][k - 1];
        dfs2(v, u);
    }
}

signed main()
{
    n = read(), k = read();
    for(int i = 1, u, v; i < n; ++i) {
        u = read(), v = read();
        add_edge(u, v), add_edge(v, u);
    }
    dfs(1, 0), dfs2(1, 0);
    for(int i = 1; i <= n; ++i) sum += ans[i];
    printf("%lld\n", sum / 2);
    return 0;
}
posted @ 2022-04-03 21:16  Suzt_ilymtics  阅读(105)  评论(2编辑  收藏  举报