多校省选模拟1

RT

A. 序列

考虑如果直接算,很容易算重,于是正难则反,算有不多彩的序列包含的 \(a\) 的个数。

于是对序列 \(a\) 进行分类讨论。

(1)若 \(a\) 已经是多彩的序列,那么其他位置选什么都不重要,不存在不多彩的序列包含 \(a\),答案就是 \((n - m + 1) * k^{n - m}\)

(2)若 \(a\) 中含有重复的元素,则一定不存在跨过 \(a\) 的多彩子序列,于是从 \(a\) 的左右两边分别进行 \(dp\)
左右两边的 \(dp\) 基本一样,这里只介绍从 \(a\) 的右边开始 \(dp\)
\(f_{i, j}\) 表示在 \(a\) 后边接一个长度为 \(i\) 的序列结尾有长度为 \(j\) 的极长多彩子序列的方案数初始状态设为: \(f_{0, l} = 1\) 其中 \(l\) 为序列 \(a\) 右边结尾的极长多彩子序列的长度。
转移为:

\[f_{i, j} = (k - j + 1) f_{i - 1, j - 1} + \sum_{l = j}^{k - 1} f_{i - 1, l} \]

式子两部分分别表示在序列结尾新填入一个与极长子序列内任意数不同的数,或填入一个与极长子序列内某个数相同的数。
不难发现这个式子可以用前\后缀合优化。
最终将左右两边合并为一个类似卷积的形式。

(3)若 \(a\) 中不含有重复的元素

直接不好算,考虑转化。
不难发现问题等价于求有多少长度为 \(n\) 的序列中包含长度为 \(m\) 的多彩子序列,而不包含长度为 \(k\) 的多彩子序列,但是这样会将所有长度为 \(m\) 的多彩子序列都算一遍,所以最后要除去 \(A_m^k\)
考虑如何 \(dp\) ,其实和第二种类似,但是还要求长度为 \(m\) 的多彩子序列出现的次数,于是再开一个 \(dp\) 数组,转移同 \(f\) 一样,但是若当前结尾的极长子序列大于等于 \(m\) ,就要令其加上 \(f\)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 3e4 + 10, K = 500, mod = 1e9 + 7;
inline int read(){
	int f = 1, x = 0; char ch = getchar();
	while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }
	while(isdigit(ch)) { x = x * 10 + ch - 48; ch = getchar(); }
	return f * x;
}

int qp(int n, int m){
	int res = 1;
	while(m){
		if(m & 1) res = 1ll * res * n % mod;
		m >>= 1, n = 1ll * n * n % mod;
	}
	return res;
}
int n, m, k, ans;
int a[N], num[N];
int f[N][K], g[N][K], mul[N];
void solve1(){
	mul[0] = 1;
	for(int i = 1; i <= k; ++i) mul[i] = 1ll * mul[i - 1] * i % mod;
	f[0][0] = 1;
	for(int i = 1; i <= n; ++i){
		for(int j = 1; j < k; ++j){
			f[i][j] += 1ll * f[i - 1][j - 1] * (k - j + 1) % mod;
			if(j > 1) (f[i - 1][j - 1] += f[i - 1][j - 2]) %= mod;
			(f[i][j] += mod - f[i - 1][j - 1]) %= mod;
			
			g[i][j] += 1ll * g[i - 1][j - 1] * (k - j + 1) % mod;
			if(j > 1) (g[i - 1][j - 1] += g[i - 1][j - 2]) %= mod;
			(g[i][j] += mod - g[i - 1][j - 1]) %= mod;
		}
		(f[i - 1][k - 1] += f[i - 1][k - 2]) %= mod;
		(g[i - 1][k - 1] += g[i - 1][k - 2]) %= mod;
		for(int j = 1; j < k; ++j){
			(f[i][j] += f[i - 1][k - 1]) %= mod;
			(g[i][j] += g[i - 1][k - 1]) %= mod;
			if(j >= m) (g[i][j] += f[i][j]) %= mod;
		}
	}
	for(int i = 1; i < k; ++i) (ans += g[n][i]) %= mod;
	ans = 1ll * ans * mul[k - m] % mod * qp(mul[k], mod - 2) % mod;
}
void solve2(){
	int lenl = 0, lenr = 0;
	memset(num, 0, sizeof num);
	for(int i = 1; i <= m; ++i){
		if(num[a[i]] && num[a[i]] < i) { lenl = i - 1; break; }
		num[a[i]] = i;
	}
	memset(num, 0, sizeof num);
	for(int i = m; i; --i){
		if(num[a[i]] > i) { lenr = m - i; break; }
		num[a[i]] = i;
	}
	f[0][lenl] = 1, g[0][lenr] = 1;
	for(int i = 1; i <= n - m; ++i){
		for(int j = 1; j < k; ++j){
			f[i][j] += 1ll * f[i - 1][j - 1] * (k - j + 1) % mod;
			if(j > 1) (f[i - 1][j - 1] += f[i - 1][j - 2]) %= mod;
			(f[i][j] += mod - f[i - 1][j - 1]) %= mod;
			
			g[i][j] += 1ll * g[i - 1][j - 1] * (k - j + 1) % mod;
			if(j > 1) (g[i - 1][j - 1] += g[i - 1][j - 2]) %= mod;
			(g[i][j] += mod - g[i - 1][j - 1]) %= mod;
		}
		(f[i - 1][k - 1] += f[i - 1][k - 2]) %= mod;
		(g[i - 1][k - 1] += g[i - 1][k - 2]) %= mod;
		for(int j = 1; j < k; ++j){
			(f[i][j] += f[i - 1][k - 1]) %= mod;
			(g[i][j] += g[i - 1][k - 1]) %= mod;
		}
	}
	for(int i = 1; i < k; ++i){
		(f[n - m][i] += f[n - m][i - 1]) %= mod;
		(g[n - m][i] += g[n - m][i - 1]) %= mod;
	}
	for(int i = 0; i <= n - m; ++i)
		(ans += 1ll * f[i][k - 1] * g[n - m - i][k - 1] % mod) %= mod;
}
signed main(void){
	freopen("a.in", "r", stdin);
	n = read(), k = read(), m = read();
	if(n < k) { puts("0"); return 0; }
	for(int i = 1; i <= m; ++i) a[i] = read();
	int mx = 0;
	for(int i = 1, cnt = 1; i <= m; ++i, ++cnt){
		if(num[a[i]]) cnt = min(cnt, i - num[a[i]]);
		mx = max(mx, cnt); num[a[i]] = i;
	}
	if(mx >= k) ans = 0;
	else if(mx == m) solve1();
	else solve2();
	ans = (1ll * (n - m + 1) * qp(k, n - m) % mod - ans + mod) % mod;
	printf("%d\n", ans);
	return 0;
}

B. 有向图

由题目中 \(20 \%\) 的提示,可以想到用随机化能很快的求出单个有趣的点(判断以它为根的 \(dfs\) 树中是否有横插边即可)。
于是考虑已知某个有趣的点,如何较快的求出所有有趣的点。
首先建出 \(dfs\) 树,不难发现树中没有横插边,由题可知任意两点相互可达,则每个点的子树内至少有一条返祖边,若某个点的子树内有超过一条返祖边,则这个点到达它祖先的路径超过一种,这个点一定不有趣,若子树内的返祖边联想不有趣的点,则这个点一定也不有趣。
于是大力 \(dfs\) 即可。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
inline int read(){
	int f = 1, x = 0; char ch = getchar();
	while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }
	while(isdigit(ch)) { x = x * 10 + ch - 48; ch = getchar(); }
	return f * x;
}

int T, n, m, id[N];
bool vis[N], in[N];
bool ab[N];
vector<int> anc[N], l[N];
bool chk(int u){
	vis[u] = in[u] = 1;
	for(int v : l[u]){
		if(!in[v] && vis[v]) return in[u] = 0;
		if(!in[v] && !vis[v] && !chk(v)) return in[u] = 0;
	}
	in[u] = 0;
	return 1;
}

void dfs(int u){
	ab[u] = in[u] = 1; anc[u].clear();
	for(int v : l[u]){
		if(in[v]) { anc[u].push_back(v); continue; }
		dfs(v);
		for(int j : anc[v])
			if(j ^ u) anc[u].push_back(j);
	}
	in[u] = 0;
	if(anc[u].size() > 1) ab[u] = 0;
}
void dfs1(int u){
	in[u] = 1;
	if(anc[u].size()) ab[u] &= ab[anc[u][0]];
	for(int v : l[u]) if(!in[v]) dfs1(v);
	in[u] = 0;
}

signed main(void){
	T = read();
	while(T--){
		n = read(), m = read(); srand(time(0));
		for(int i = 1; i <= n; ++i) l[i].clear(), id[i] = i;
		for(int i = 1, x, y; i <= m; ++i){
			x = read(), y = read();
			l[x].push_back(y);
		}
		random_shuffle(id + 1, id + 1 + n);
		for(int i = 1; i <= n + 1; ++i){
			if(i >= 50 || i == n + 1) { puts("-1"); break; }
			memset(vis, 0, sizeof(bool) * (n + 1));
			if(!chk(id[i])) continue;
			int u = id[i]; dfs(u); dfs1(u);
			for(int j = 1; j <= n; ++j)
				if(ab[j]) printf("%d ", j);
			puts(""); break;
		}
	}
	return 0;
}

C. 图形

又是神仙 \(dp\) 。。。

首先有一个较为显然的转换,这个题可以看成求一下方程的解:

\[\sum_{i = 1}^n c_i x_i = 0 \]

\[\sum_{i = 1}^n c_i y_i = 0 \]

\[\sum_{x_i > 0} c_i a_i \le m \]

\[\sum_{y_i > 0} c_i y_i \le m \]

考虑直接枚举每一个 \(c_i\) ,不难发现 \(c_i\)\(m\) 同级,大力枚举显然会 \(T\) ,因此考虑优化枚举,不难发现我们只关注解的数量,而不关注解具体是什么,所以可以想到 鬼知道是怎么想到的 按位算,每次枚举这一位上每个 \(c_i\) 的值是什么,一共只有 \(2^n\) 种情况,同时记录将这一位上正负 \(x_i, y_i\) 的和记录在状态里,还要记录这一位上 \(x\) 的和与 \(y\) 的和是否大于 \(m\) 这一位上的数。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 10, mod = 998244353;
inline int read(){
	int f = 1, x = 0; char ch = getchar();
	while(!isdigit(ch)) { if(ch == '-') f = -1; ch = getchar(); }
	while(isdigit(ch)) { x = x * 10 + ch - 48; ch = getchar(); }
	return f * x;
}

int f[31][30][30][30][30][2][2];
int a[N], b[N], n, m;

bool chk(int x, int y, int z) { return x ^ y ? x < y : z; }

int F(int p, int px, int py, int nx, int ny, bool cx, bool cy){
	if(p == 30) return (!px && !py && !nx && !ny && !cx && !cy);
	int &x = f[p][px][py][nx][ny][cx][cy], ul = (m >> p) & 1;
	if(~x) return x; x = 0;
	for(int i = 0; i < 1 << n; ++i){
		int PX = px, PY = py, NX = nx, NY = ny;
		for(int j = 0; j < n; ++j) if((i >> j) & 1){
			(a[j] > 0 ? PX : NX) += abs(a[j]);
			(b[j] > 0 ? PY : NY) += abs(b[j]);
		}
		if(((PX & 1) == (NX & 1)) && ((PY & 1) == (NY & 1)))
			(x += F(p + 1, PX >> 1, PY >> 1, NX >> 1, NY >> 1, chk(ul, PX & 1, cx), chk(ul, PY & 1, cy))) %= mod;
	}
	return x;
}

signed main(void){
	n = read(), m = read();
	for(int i = 0; i < n; ++i)
		a[i] = read(), b[i] = read();
	memset(f, -1, sizeof f);
	printf("%d\n", (F(0, 0, 0, 0, 0, 0, 0) - 1 + mod) % mod);
	return 0;
}
posted @ 2022-01-11 19:03  Cyber_Tree  阅读(48)  评论(0编辑  收藏  举报