zoukankan      html  css  js  c++  java
  • 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 ? ' ' : '
    ');
    	
    	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} floor} { 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
    ", f[n]);
    	
    	return 0;
    }
    

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

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

    但对于偶数,由于是双重心我们可能会重复计算。我的方法是先限制每个子树的大小不超过 (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
    ", f[n]);
    	else printf("%lld
    ", (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} floor} {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
    ", 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
    ", 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
    ", ans);
    	}
    	
    	return 0;
    }
    
  • 相关阅读:
    人工智能背后的故事
    idea 开发插件。
    安卓工作室 Android studio 或 Intellij IDEA 美化 修改 汉化 酷炫 装逼 Android studio or Intellij IDEA beautify modify Chinesization cool decoration
    安卓工作室 android studio文件和代码模板,以及汉化出错问题
    安卓工作室 android studio 汉化后,报错。 设置界面打不开。Can't find resource for bundle java.util.PropertyResourceBundle, key emmet.bem.class.name.element.separator.label
    android studio的汉化 教程 及解析
    安卓工作室Android Studio 快捷键
    安卓工作室 android studio 的 汉化 美化 定制 Android studio's Chinesization beautification customization
    VR开发 VR development
    Lakeshore 中文开发界面,示例项目,飞机大战 等 Lakeshore Chinese development interface, sample project, aircraft war, etc
  • 原文地址:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/8124845.html
Copyright © 2011-2022 走看看