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;
    }
    
  • 相关阅读:
    Entity Framework开源了
    动态执行超过4000个字符的SQL
    微软针对Access提供了免费的SQL Server移植工具——SSMA
    网络采集库NCrawler
    使用WinSCP软件在windows和Linux中进行文件传输
    Open Source C# (Mono Compatible) Library for Sending Push Notifications To iOS (iPhone/iPad APNS), Android (C2DM), Windows Phone Apps
    Mono 2.11.3 发布包含微软的 Entity Framework
    谷歌发布 AngularJS 1.0,允许扩展HTML语法
    Accord.NET Framework
    CentOS源码编译安装Nginx和tcp_proxy module
  • 原文地址:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/8124845.html
Copyright © 2011-2022 走看看