2018冬令营模拟测试赛(四)

2018冬令营模拟测试赛(四)

[Problem A]挑战NPC

试题描述

TAT

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

这题不知为何我暴力 dfs 搜了 \(90\) 分……

我们可以考虑最苛刻的条件,即 \(m = n - 1\)\(p = 3\)

这样的话就是一棵树,我们要从一个根节点出发,最终回到根节点的某一个儿子。

将树二分染色,令根为黑色。那么对于黑色节点的子树就先经过这个点然后往子树里面跳,最终回到某个儿子(白),然后跳到另一个儿子(白)的一个儿子(黑)中,对于那个黑点重复这种过程;对于白色节点的子树,从前面的过程可以看出我进入这个子树的时候,已经在某个儿子当中了,我的目标就是最后出来的时候踩在这个字数的白根上。这显然是可以实现的,对于黑点我们先输出路径再递归,白点先递归在输出即可。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
    int x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

#define maxn 1010
#define maxm 2010

int n, fa[maxn], col[maxn];
int findset(int x) { return x == fa[x] ? x : fa[x] = findset(fa[x]); }

int m, head[maxn], nxt[maxm], to[maxm];
void AddEdge(int a, int b) {
    to[++m] = b; nxt[m] = head[a]; head[a] = m;
    swap(a, b);
    to[++m] = b; nxt[m] = head[a]; head[a] = m;
    return ;
}

void biparate(int u, int pa, int c) {
    col[u] = c;
    for(int e = head[u]; e; e = nxt[e]) if(to[e] != pa) biparate(to[e], u, 3 - c);
    return ;
}

int Path[maxn], cnt;
void solve(int u, int pa) {
    if(col[u] == 1) Path[++cnt] = u;
    for(int e = head[u]; e; e = nxt[e]) if(to[e] != pa) solve(to[e], u);
    if(col[u] == 2) Path[++cnt] = u;
    return ;
}

int main() {
    n = read(); int m = read(); read();
    rep(i, 1, n) fa[i] = i;
    rep(i, 1, m) {
        int a = read(), b = read(), u = findset(a), v = findset(b);
        if(u != v) fa[v] = u, AddEdge(a, b);
    }
    
    biparate(1, 0, 1);
    solve(1, 0);
    
    rep(i, 1, n) printf("%d%c", Path[i], i < n ? ' ' : '\n');
    
    return 0;
}

[Problem B]仙人掌

试题描述

T_T

Q_Q

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

本题全场最难,看了题解还是觉得很烧脑……

我们一问一问来。

有根树的情况,令 \(f_i\) 表示 \(i\) 个节点的有根树的异构体个数,直接转移的话需要枚举有多少个儿子,每个儿子的大小,显然是指数级的复杂度。

考虑换一种 dp 顺序。我们每次把大小为 \(i\) 的有根树的贡献加进去,即每次都更新一下整个 dp 数组 \(f_{i+1} \sim f_n\)\(f_1 \sim f_i\) 不可能再被更新了,因为它们撑不下一个大小为 \(i\) 的子树),在我 dp 进行到第 \(i\) 轮之前,我的所有子树大小都最多只有 \(i-1\) 那么大。那么所有 dp 值都缺少 \(f_i\) 的贡献,于是就给每个 dp 值加上有大小为 \(i\) 的子树的情况,我们需要枚举它们需要多少个大小为 \(i\) 的子树:\(\forall j \in (i, n], f_j += \sum_{t=1}^{\lfloor \frac{j}{i} \rfloor} { C_t^{f_i + t - 1} \cdot f'_{j-it} }\) 注意式中的 \(f'\) 表示的是加入大小为 \(i\) 的子树之前的 dp 值,在实现的过程中我们可以倒序枚举 \(j\),避免前面对后面的影响。注:\(C_t^{f_i + t - 1}\) 表示的是从 \(f_i\) 种物品中选择 \(t\) 个,每种物品可以选无限次的方案数,至于为什么是这个可以自行百度(提示:转化成插板问题)。

这一问 \(30\) 分:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <map>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
    int x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

#define maxn 3010
#define LL long long

int n, MOD, f[maxn], inv[maxn];

int C[maxn][maxn]; // C[i][j]: f[i] choose j, can choose many times.
int Pow(int a, int b) {
    int ans = 1, t = a;
    while(b) {
        if(b & 1) ans = (LL)ans * t % MOD;
        t = (LL)t * t % MOD; b >>= 1;
    }
    return ans;
}

int main() {
    n = read(); MOD = read();
    
    inv[1] = 1;
    rep(i, 2, n) inv[i] = (LL)(MOD - MOD / i) * inv[MOD%i] % MOD;
    f[1] = 1;
    rep(i, 1, n) {
        C[i][0] = 1;
        rep(t, 1, n / i) C[i][t] = (LL)C[i][t-1] * (f[i] + t - 1) % MOD * inv[t] % MOD;
        dwn(j, n, i + 1) rep(t, 1, j / i) {
            f[j] += (LL)C[i][t] * f[j-t*i] % MOD;
            if(f[j] >= MOD) f[j] -= MOD;
        }
    }
    printf("%d\n", f[n]);
    
    return 0;
}

对于无根树,也是这样 dp,只是在根节点上面特判一下每个子树的大小不能超过 \(\lfloor \frac{n}{2} \rfloor\),在上面贡献的时候限制一下就好了。

对于奇数的情况很容易(直接按照上述方法计算即可),因为不会有重复计数的问题,它重心唯一。

但对于偶数,由于是双重心我们可能会重复计算。我的方法是先限制每个子树的大小不超过 \(\frac{n}{2} - 1\),然后在处理两棵 \(\frac{n}{2}\) 的子树对顶的情况,就是在 \(f_{\frac{n}{2}}\) 中选择两个(可重复),用这个方案加上之前算的 \(f_n\) 就好了。

这一问 \(50\) 分:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <map>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
    int x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

#define maxn 3010
#define LL long long

int n, MOD, f[maxn], inv[maxn];

int C[maxn][maxn]; // C[i][j]: f[i] choose j, can choose many times.
int Pow(int a, int b) {
    int ans = 1, t = a;
    while(b) {
        if(b & 1) ans = (LL)ans * t % MOD;
        t = (LL)t * t % MOD; b >>= 1;
    }
    return ans;
}

int main() {
    n = read(); MOD = read();
    
    inv[1] = 1;
    rep(i, 2, n) inv[i] = (LL)(MOD - MOD / i) * inv[MOD%i] % MOD;
    f[1] = 1;
    rep(i, 1, (n & 1) ? n / 2 : n / 2 - 1) {
        C[i][0] = 1;
        rep(t, 1, n / i) C[i][t] = (LL)C[i][t-1] * (f[i] + t - 1) % MOD * inv[t] % MOD;
        dwn(j, n, i + 1) rep(t, 1, j / i) {
            f[j] += (LL)C[i][t] * f[j-t*i] % MOD;
            if(f[j] >= MOD) f[j] -= MOD;
        }
    }
    if(n & 1) printf("%d\n", f[n]);
    else printf("%lld\n", (f[n] + (LL)f[n/2] * (f[n/2] + 1) / 2 % MOD) % MOD);
    
    return 0;
}

接下来就进入到最丧的仙人掌了。首先仙人掌可以转化成“圆方树”,然后“方点”不占体积,且只能连接“圆点”。

\(f_i\) 的定义不变,现在由于有环,那么方点的儿子就是有序的了,所以我们需要再维护一些值用来辅助计算。

\(cac_{0, i}\) 表示一个“方点”为根的,子树个数 \(\ge 1\) 个,大小为 \(i\)(注意方点不算在“大小”中)的异构树的数目;

\(cac_{1, i}\) 表示一个“方点”为根的,子树个数 \(\ge 2\) 个,大小为 \(i\) 的异构树的数目;(为什么要分开记呢?因为不能有重边,对于这个有根树,如果它只有一个“圆点”儿子,那么在加上父亲的“圆点”只有两个点构成一个环,就是重边了)。

以上两个我们都还没有考虑环的对称同构,所以会重复计数,下面这个就要考虑了。

\(cir_i\) 表示一个“方点”为根的,子树个数 \(\ge 2\) 个,大小为 \(i\) 的异构树的数目(考虑对称同构)。

转移:\(cac_{1, i} = \sum_{j=1}^{n-1} {cac_{0, j} \cdot f_{i-j}}\)\(cac_{0, i} = cac_{1, i} + f_i\)\(cir_i = cac_{1, i} + \sum_{j=1}^{\lfloor \frac{i}{2} \rfloor} {cac_{0, j} \cdot f_{i-2j}}\)

\(\ge 1\) 的“方”根树强塞一个儿子就是 \(\ge 2\) 的“方”根树,\(\ge 2\) 的“方”根树补上只有一个子树的情况就是 \(\ge 1\) 的“方”根树,对称同构就是 \(\frac{不对称情况+对称情况}{2}\) 来去重。

这一问 \(100\) 分:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <map>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
    int x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

#define maxn 3010
#define LL long long

int n, MOD, f[maxn], cac[2][maxn], cir[maxn], inv[maxn];

int C[maxn][maxn]; // C[i][j]: f[i] choose j, can choose many times.
int Pow(int a, int b) {
    int ans = 1, t = a;
    while(b) {
        if(b & 1) ans = (LL)ans * t % MOD;
        t = (LL)t * t % MOD; b >>= 1;
    }
    return ans;
}

int main() {
    n = read(); MOD = read();
    
    inv[1] = 1;
    rep(i, 2, n) inv[i] = (LL)(MOD - MOD / i) * inv[MOD%i] % MOD;
    f[1] = 1;
    rep(i, 1, n) {
        rep(j, 1, i - 1) {
            cac[1][i] += (LL)cac[0][j] * f[i-j] % MOD;
            if(cac[1][i] >= MOD) cac[1][i] -= MOD;
        }
        cac[0][i] = (cac[1][i] + f[i]) % MOD;
        cir[i] = cac[1][i];
        rep(j, 1, i >> 1) {
            cir[i] += (LL)cac[0][j] * max(1, f[i-2*j]) % MOD;
            if(cir[i] >= MOD) cir[i] -= MOD;
        }
        cir[i] = (LL)cir[i] * inv[2] % MOD;
        
        C[i][0] = 1;
        rep(j, 1, n / i) C[i][j] = (LL)C[i][j-1] * (f[i] + cir[i] + j - 1) % MOD * inv[j] % MOD;
        dwn(j, n, i + 1) rep(t, 1, j / i) {
            f[j] += (LL)C[i][t] * f[j-i*t] % MOD;
            if(f[j] >= MOD) f[j] -= MOD;
        }
    }
    printf("%d\n", f[n]);
    
    return 0;
}

[Problem C]森林

试题描述

TwT

QwQ

输入

见“试题描述

输出

见“试题描述

输入示例

见“试题描述

输出示例

见“试题描述

数据规模及约定

见“试题描述

题解

又是一道 LCT 维护 dp 信息的题。这题对于虚边连出去的点直接暴力开个 map 维护每个子树的最大深度以及每个最大深度有多少个节点就好了。然后在 splay 中维护到最浅点的信息,由于要换根需要翻转,所以需要再维护一个到最深点的信息。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <map>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
    int x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

#define maxn 400010

int ans;
struct LCT {
    int fa[maxn], ch[maxn][2], siz[maxn], Ld[maxn], Lc[maxn], Rd[maxn], Rc[maxn], S[maxn], top;
    bool rev[maxn];
    map <int, int> tot[maxn];
    
    bool isrt(int u) { return ch[fa[u]][0] != u && ch[fa[u]][1] != u; }
    void maintain(int u) {
        siz[u] = 1;
        map <int, int> :: iterator it = tot[u].end(); it--;
        int ls = ch[u][0] ? siz[ch[u][0]] : 0, rs = ch[u][1] ? siz[ch[u][1]] : 0;
        Ld[u] = it->first + ls; Rd[u] = it->first + rs; Lc[u] = Rc[u] = it->second;
        if(ch[u][0]) {
            siz[u] += siz[ch[u][0]];
            if(Ld[u] < Ld[ch[u][0]]) Ld[u] = Ld[ch[u][0]], Lc[u] = Lc[ch[u][0]];
            else if(Ld[u] == Ld[ch[u][0]]) Lc[u] += Lc[ch[u][0]];
            if(Rd[u] < Rd[ch[u][0]] + rs + 1) Rd[u] = Rd[ch[u][0]] + rs + 1, Rc[u] = Rc[ch[u][0]];
            else if(Rd[u] == Rd[ch[u][0]] + rs + 1) Rc[u] += Rc[ch[u][0]];
        }
        if(ch[u][1]) {
            siz[u] += siz[ch[u][1]];
            if(Ld[u] < Ld[ch[u][1]] + ls + 1) Ld[u] = Ld[ch[u][1]] + ls + 1, Lc[u] = Lc[ch[u][1]];
            else if(Ld[u] == Ld[ch[u][1]] + ls + 1) Lc[u] += Lc[ch[u][1]];
            if(Rd[u] < Rd[ch[u][1]]) Rd[u] = Rd[ch[u][1]], Rc[u] = Rc[ch[u][1]];
            else if(Rd[u] == Rd[ch[u][1]]) Rc[u] += Rc[ch[u][1]];
        }
        return ;
    }
    void pushdown(int u) {
        if(!rev[u]) return ;
        if(ch[u][0]) rev[ch[u][0]] ^= 1, swap(Ld[ch[u][0]], Rd[ch[u][0]]), swap(Lc[ch[u][0]], Rc[ch[u][0]]), swap(ch[ch[u][0]][0], ch[ch[u][0]][1]);
        if(ch[u][1]) rev[ch[u][1]] ^= 1, swap(Ld[ch[u][1]], Rd[ch[u][1]]), swap(Lc[ch[u][1]], Rc[ch[u][1]]), swap(ch[ch[u][1]][0], ch[ch[u][1]][1]);
        rev[u] = 0;
        return ;
    }
    void rotate(int u) {
        int y = fa[u], z = fa[y], l = 0, r = 1;
        if(!isrt(y)) ch[z][ch[z][1]==y] = u;
        if(ch[y][1] == u) swap(l, r);
        fa[u] = z; fa[y] = u; fa[ch[u][r]] = y;
        ch[y][l] = ch[u][r]; ch[u][r] = y;
        maintain(y);
        return ;
    }
    void splay(int u) {
        int t = u; S[++top] = u;
        while(!isrt(t)) S[++top] = fa[t], t = fa[t];
        while(top) pushdown(S[top--]);
        while(!isrt(u)) {
            int y = fa[u], z = fa[y];
            if(!isrt(y)) {
                if(ch[y][0] == u ^ ch[z][0] == y) rotate(u);
                else rotate(y);
            }
            rotate(u);
        }
        return maintain(u);
    }
    
    void AddTotal(int u, int son) {
        if(!son) return ;
        if(!tot[u].count(Ld[son] + 1)) tot[u][Ld[son]+1] = Lc[son];
        else tot[u][Ld[son]+1] += Lc[son];
        return ;
    }
    void DelTotal(int u, int son) {
        if(!son) return ;
        tot[u][Ld[son]+1] -= Lc[son];
        if(!tot[u][Ld[son]+1]) tot[u].erase(Ld[son] + 1);
        return ;
    }
    void access(int u) {
        splay(u); AddTotal(u, ch[u][1]); ch[u][1] = 0; maintain(u);
        while(fa[u]) {
            splay(fa[u]); AddTotal(fa[u], ch[fa[u]][1]);
            DelTotal(fa[u], u); ch[fa[u]][1] = u; maintain(fa[u]);
            splay(u);
        }
        return ;
    }
    void makeroot(int u) {
        access(u);
        ans -= Lc[u];
        rev[u] ^= 1; swap(Ld[u], Rd[u]); swap(Lc[u], Rc[u]); swap(ch[u][0], ch[u][1]);
        ans += Lc[u];
        return ;
    }
    void link(int a, int b) {
        makeroot(a); access(b);
        ans -= Lc[a] + Lc[b];
        fa[a] = b; AddTotal(b, a); maintain(b);
        ans += Lc[b];
        return ;
    }
    void cut(int a) {
        access(a);
        ans -= Lc[a];
        int b = ch[a][0]; fa[b] = ch[a][0] = 0; maintain(a);
        ans += Lc[a] + Lc[b];
        return ;
    }
} sol;

int n, q;

int main() {
    n = read(); q = read(); ans = n;
    rep(i, 1, n) sol.tot[i][0] = 1, sol.maintain(i);
    rep(i, 1, n) {
        int u = read();
        if(u) sol.link(i, u);
    }
    printf("%d\n", ans);
    while(q--) {
        int op = read(), a = read(), b;
        if(op == 1) {
            b = read();
            sol.link(a, b);
        }
        if(op == 2) sol.cut(a);
        printf("%d\n", ans);
    }
    
    return 0;
}

转载于:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/8124845.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值