2022.7.22 模拟赛

T1 tree

  首先,谢谢出题人 /wx

在这里插入图片描述

  要求求出把一棵树分成若干个联通的部分的方案数。

  首先我们很容易想到,分出来的 “子树” 的大小 d d d 是满足 d ∣ n d | n dn 的。也就是说我们只需要判断每一个 d d d 是否可行的就好了,其中 d d d 的个数是等于 σ 0 ( n ) < n \sigma_0(n) < \sqrt{n} σ0(n)<n 的。

  判断可行是挺简单的,你只用写一个 d f s dfs dfs 模拟一下分树的过程就好了,所以 c h e c k check check 就是 O ( n ) O(n) O(n) 的。那么总的复杂度就是小于 O ( n n ) O(n\sqrt{n}) O(nn ) 的。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 2002000
#define MAXM MAXN << 2
#define MOD 998244353

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x;
}

int n = 0;
int cnt = 0;
int d[MAXN] = { 0 };
int tot = 0;
int first[MAXN] = { 0 };
int   nxt[MAXM] = { 0 };
int    to[MAXM] = { 0 };
inline void add(int x, int y){
	nxt[++tot] = first[x];
	first[x] = tot; to[tot] = y;
}

int sz[MAXN] = { 0 };
void prework(int x, int fa){
	sz[x]++;
	for(int e = first[x]; e; e = nxt[e]){
		int y = to[e];
		if(y == fa) continue;
		prework(y, x);
		sz[x] += sz[y];
	}
}

int check(int x, int fa, int w){
	int siz = 1;
	for(int e = first[x]; e; e = nxt[e]){
		int y = to[e];
		if(y == fa) continue;
		if(sz[y] >= w){                         // 大于先继续向下 
			int f = check(y, x, w);
			if(f == -1) return -1;
			siz += f;	                        // 加上下面的贡献 
		}
		else siz += sz[y];
		if(siz > w) return -1;                  // 全部木大 
	}
	if(siz == w) return 0;                      // 等于直接剪掉 不算贡献 
	return siz;
}

int main(){
//	freopen("a.in", "r", stdin);
	n = in; int ans = 0;
	for(int i = 1; i < n; i++){
		int x = in, y = in;
		add(x, y), add(y, x);
	} prework(1, 0);
	for(int i = 1; i * i <= n; i++)
		if(n % i == 0) d[++cnt] = i;
	for(int i = cnt; i >= 1; i--)
		if(n / d[i] != d[i]) d[++cnt] = n / d[i];
//	for(int i = 1; i <= cnt; i++) cout << d[i] << ' '; puts("");
	for(int i = 1; i <= cnt; i++)
		if(check(1, 0, d[i]) != -1) ans = (ans + 1) % MOD;
	cout << ans << '\n';
	return 0;
} 

  但是这样的写法似乎会 T T T 2 , 3 2,3 23 个点,所以我们还要继续看看有没有什么性质。

  我们考虑找到 s i z e size size d d d 的倍数的关键点(因为我们刚才模拟拆树的时候就是找的这些点拆)。我们考虑找到这些点的数量上限,我们把这些关键点都提取出来,组成的新树中的叶子节点的 s i z e size size 至少是 d d d,而且非叶子节点的 s i z e size size 肯定比它所有儿子的 s i z e size size 和要大,且至少大 d d d。所以显然关键点的个数上限就是 n d \frac nd dn

  如果我们恰好能找到 n d \frac nd dn 个关键点,就说明每个点恰好是一颗大小为 d d d 的子树的根,并且这些子树互不包含。也就是说,如果恰有 n d \frac nd dn 个关键点,题目的要求就能成立。所以我们可以通过这个来重新写一下 c h e c k ( ) check() check() 函数就能 A A A 了。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 1001000
#define MAXM MAXN << 2

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x;
}

int n = 0;
int d[MAXN] = { 0 };
int tot = 0;
int first[MAXN] = { 0 };
int   nxt[MAXM] = { 0 };
int    to[MAXM] = { 0 };
inline void add(int x, int y){
	nxt[++tot] = first[x];
	first[x] = tot; to[tot] = y;
}

int siz[MAXN] = { 0 };
int cnt[MAXN] = { 0 }; 
void prework(int x, int fa){
	siz[x]++;
	for(int e = first[x]; e; e = nxt[e]){
		int y = to[e];
		if(y == fa) continue;
		prework(y, x);
		siz[x] += siz[y];
	}
	cnt[siz[x]]++;
}

bool check(int d){
	int num = 0;
	for(int i = d; i <= n; i += d)
		num += cnt[i];
	return num == (n / d);
}

int main(){
	n = in; int ans = 0;
	for(int i = 1; i < n; i++){
		int x = in, y = in;
		add(x, y), add(y, x);
	} prework(1, 0);
	for(int i = 1; i <= n; i++)
		if(n % i == 0) ans += check(i);
	cout << ans << '\n'; 
	return 0;
} 

T2 seq

30 p t s 30pts 30pts 就是一个简单的 d p dp dp。我们令 f i , j f_{i, j} fi,j 表示前 i i i 个数字,最后一位是 j j j 的最小值,那么有:

f i , j = min ⁡ j = a i A { min ⁡ k = a i − 1 A { f i − 1 , k + c ∣ j − k ∣ + ( a i − j ) 2 } } f_{i, j} = \min_{j = a_i}^A \{\min_{k = a_{i - 1}}^A\{ f_{i - 1, k} + c|j - k| + (a_i - j)^2 \}\} fi,j=j=aiminA{k=ai1minA{fi1,k+cjk+(aij)2}}

  初始化就是 f 1 , j = ( j − a 1 ) 2 j ∈ [ a i , A ] f_{1, j} = (j - a_1)^2 \quad j \in[a_i, A] f1,j=(ja1)2j[ai,A],其中 A A A a i a_i ai 的值域。这样做的时间复杂度是 O ( n A 2 ) O(nA^2) O(nA2) 的。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 1010
#define INFI 1 << 30

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x;
}

int a[MAXN] = { 0 };
int n = 0; int c = 0;
int f[MAXN][MAXN] = { 0 };

int main(){
	n = in; c = in; int A = 0, ans = INFI;
	for(int i = 1; i <= n; i++) a[i] = in, A = 101;
	memset(f, 0x3f, sizeof(f));
	for(int j = a[1]; j <= A; j++) f[1][j] = (j - a[1]) * (j - a[1]);
	for(int i = 2; i <= n; i++)
		for(int j = a[i]; j <= A; j++)
			for(int k = a[i - 1]; k <= A; k++)
				f[i][j] = min(f[i][j], f[i - 1][k] + c * abs(j - k) + (j - a[i]) * (j - a[i]));
	for(int i = 1; i <= A; i++) ans = min(ans, f[n][i]);
	cout << ans << '\n';
	return 0;
} 

  我们考虑优化这个做法。我们把上面的式子稍微变一下形:

f i , j = min ⁡ j = a i A { min ⁡ k = a i − 1 A { f i , k + c ∣ j − k ∣ } + ( a i − j ) 2 } f_{i, j} = \min_{j = a_i}^A \{ \min_{k = a_{i - 1}}^A \{ f_{i, k} + c|j - k| \} + (a_i - j)^2 \} fi,j=j=aiminA{k=ai1minA{fi,k+cjk}+(aij)2}

  那么我们如果令 s i , j = min ⁡ k = a i − 1 A { f i , k + c ∣ j − k ∣ } s_{i, j} = \min\limits_{k = a_{i - 1}}^A \{ f_{i, k} + c|j - k| \} si,j=k=ai1minA{fi,k+cjk},并且把 s i , j s_{i, j} si,j 预处理出来,那么我们就可以少枚举一次 A A A,方程就可以写成这样:

f i , j = min ⁡ j = a i A { s i , j + ( a i − j ) 2 } f_{i, j} = \min_{j = a_i}^A \{ s_{i, j} + (a_i - j)^2 \} fi,j=j=aiminA{si,j+(aij)2}

  现在我们考虑如何预处理出 s i , j s_{i, j} si,j。我们考虑如果把绝对值拆开就很好转移了,也就是我们的 s i , j s_{i, j} si,j 如果等于 min ⁡ k = a i − 1 A { f i , k + c ( j − k ) } \min\limits_{k = a_{i - 1}}^A \{ f_{i, k} + c(j - k) \} k=ai1minA{fi,k+c(jk)} 这样就好处理多了,因为我们可以:

min ⁡ { f i , k + c ( j − k ) } = min ⁡ { min ⁡ { f i , k + c ( j − 1 − k ) } + c , f i − 1 , j } \min \{ f_{i, k} + c(j - k) \} = \min \{ \min \{f_{i, k} + c(j - 1 - k) \} + c, f_{i - 1, j} \} min{fi,k+c(jk)}=min{min{fi,k+c(j1k)}+c,fi1,j}

  也就是说:

s i , j = min ⁡ { s i , j − 1 + c , f i − 1 , j } s_{i, j} = \min \{ s_{i, j - 1} + c, f_{i - 1, j} \} si,j=min{si,j1+c,fi1,j}

  但是这里是有绝对值的,所以我们考虑正着扫一遍之后再反着扫一遍,这样就能解决绝对值的问题了(注意这里要用滚动数组来避免空间炸掉)。这样搞的时间复杂度就变成 O ( n A ) O(nA) O(nA) 的了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 100100
#define MAXA 101
#define INFI 1e18

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x;
}

int a[MAXN] = { 0 };
int n = 0; int c = 0;
int f[MAXN][MAXA] = { 0 };
int g1[MAXN] = { 0 };
int g2[MAXN] = { 0 };

signed main(){
	n = in; c = in; int A = 0, ans = INFI;
	for(int i = 1; i <= n; i++) a[i] = in, A = max(A, a[i]);
	memset(f, 0x3f, sizeof f);
	for(int j = a[1]; j <= A; j++) f[1][j] = 1ll * (j - a[1]) * (j - a[1]);
	for(int i = 2; i <= n; i++){
		for(int j = 0; j <= A + 1; j++) g1[j] = g2[j] = INFI;
		for(int j = A; j >= a[i]; j--)
			g1[j] = min(g1[j], min(g1[j + 1] + c, f[i - 1][j]));
		for(int j = a[i - 1]; j <= A; j++)
			g2[j] = min(g2[j], min(g2[j - 1] + c, f[i - 1][j]));
		for(int j = a[i]; j <= A; j++)
			f[i][j] = min(f[i][j], min(g1[j], g2[j]) + 1ll * (j - a[i]) * (j - a[i]));
	}
	for(int i = 1; i <= A; i++) ans = min(ans, f[n][i]);
	cout << ans << '\n';
	return 0;
}

T3 color

  感觉 T 3 T3 T3 才是最简单的一道题(好像是因为把原来的 T 3 T3 T3 给换掉了)。

  考场上脑抽,思路一直不对,推式子:

a n s = ∑ S ⊂ U ∣ ∑ i ∈ S a i − ∑ i ∈ C U S a i ∣ = ∑ S ⊂ U ∣ 2 ∑ i ∈ S a i − X ∣ X = ∑ i = 1 n a i \begin{aligned} ans = & \sum_{S \subset U} \bigg| \sum_{i \in S}a_i - \sum_{i \in C_US}a_i \bigg| \\ = & \sum_{S \subset U} \bigg| 2\sum_{i \in S}a_i - X \bigg| \\ X = & \sum_{i = 1}^na_i \end{aligned} ans==X=SU iSaiiCUSai SU 2iSaiX i=1nai

  本来到这一步了,解法马上就出来了,然后我就开始发病:

X = ∑ i = 1 n a i a n s = ∑ S ⊂ U ∣ ∑ i ∈ S a i − ∑ i ∈ C U S a i ∣ = ∑ S ⊂ U ∣ 2 ∑ i ∈ S a i − X ∣ = ∑ S ⊂ U ( 2 ∑ i ∈ S a i − X ) [ 2 ∑ i ∈ S a i ≥ X ] − ∑ S ⊂ U ( 2 ∑ i ∈ S a i − X ) [ 2 ∑ i ∈ S a i < X ] = ( 2 ∑ S ⊂ U ∑ i ∈ S a i − ∑ S ⊂ U X ) [ 2 ∑ i ∈ S a i ≥ X ] − ( 2 ∑ S ⊂ U ∑ i ∈ S a i − ∑ S ⊂ U X ) [ 2 ∑ i ∈ S a i < X ] \begin{aligned} X = & \sum_{i = 1}^na_i \\\\ ans = & \sum_{S \subset U} \bigg| \sum_{i \in S}a_i - \sum_{i \in C_US}a_i \bigg| \\ = & \sum_{S \subset U} \bigg| 2\sum_{i \in S}a_i - X \bigg| \\ = & \sum_{S \subset U}\left( 2\sum_{i \in S}a_i - X \right)[2\sum_{i \in S}a_i \geq X] - \sum_{S\subset U}\left( 2\sum_{i \in S}a_i - X \right)[2\sum_{i \in S}a_i < X] \\\\ = & \left(2\sum_{S \subset U}\sum_{i \in S}a_i - \sum_{S\subset U}X\right)[2\sum_{i \in S}a_i \geq X] - \left(2\sum_{S \subset U}\sum_{i \in S}a_i - \sum_{S\subset U}X\right)[2\sum_{i \in S}a_i < X] \end{aligned} X=ans====i=1naiSU iSaiiCUSai SU 2iSaiX SU(2iSaiX)[2iSaiX]SU(2iSaiX)[2iSai<X](2SUiSaiSUX)[2iSaiX](2SUiSaiSUX)[2iSai<X]

  然后就推不动了。

  实际上在上面那个式子就已经可以做了。我们令 f j f_j fj 表示选出的子集 S S S ∑ i ∈ S a i = j \sum\limits_{i \in S}a_i = j iSai=j 的集合个数。那么显然现在这个就是一个背包了。我们就有:

f j = ∑ a k ≤ j f j − a k f_j = \sum_{a_k \leq j}f_{j - a_k} fj=akjfjak

  就直接背包转移,复杂度是 O ( n S ) O(nS) O(nS)

  然后答案就很显然了:

a n s = ∑ i = 1 X f i ∣ 2 i − X ∣ ans = \sum_{i = 1}^Xf_i|2i - X| ans=i=1Xfi∣2iX

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 100100
#define MOD 998244353

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x;
}

int T = 0;
int x = 0; int n = 0;
int w[MAXN] = { 0 };
int f[MAXN] = { 0 };

int main(){
	T = in;
	while(T--){
		x = in; n = in; int ans = 0;
		f[0] = 1;
		for(int i = 1; i <= x; i++) f[i] = 0;
		for(int i = 1; i <= n; i++) w[i] = in;
		for(int i = 1; i <= n; i++)
			for(int j = x; j >= w[i]; j--)
				f[j] = (f[j] + f[j - w[i]]) % MOD; 
		for(int i = 0; i <= x; i++) ans = (ans + 1ll * f[i] * abs(2 * i - x) % MOD) % MOD;
		cout << ans << '\n';
	}
	return 0;
} 

T4 mex

  之前似乎做过一道一模一样的题…

  传送门:P4587 [FJOI2016]神秘数

  可惜考场上没想起来,也没时间写了。

T J TJ TJ 我之前就写过一份,现在直接搬过来吧。

  求区间最小的不能被子集合表示出的数。

  考虑一个区间 [ l , r ] [l, r] [l,r],对这一段进行一个升序排序,设排序之后的数分别为 a l ≤ a l + 1 ≤ ⋯ ≤ a r a_l \leq a_{l+1} \leq \cdots \leq a_r alal+1ar,现在对于 a l a_l al 如果不等于 1 1 1,那就可以直接输出 1 1 1。若等于 1 1 1 我们考虑一个简单的 d p dp dp

  现在用 p o s pos pos 表示当前能表示出的数属于的区间为 [ 1 , p o s ] [1, pos] [1,pos](很显然这个区间包括了 1 1 1),对于下一个数 a i a_i ai,现在有两种情况:

  1. 如果 a i ≤ p o s + 1 a_i \leq pos + 1 aipos+1,那么现在能表示出的数的集合就是 [ 1 , p o s ] ⋃ [ 1 + a i , p o s + a i ] = [ 1 , p o s + a i ] [1, pos] \bigcup [1 + a_i, pos + a_i] = [1, pos + a_i] [1,pos][1+ai,pos+ai]=[1,pos+ai]
  2. 如果 a i > p o s + 1 a_i> pos + 1 ai>pos+1,那么现在能表示出的数的集合就是 [ 1 , p o s ] ⋃ [ 1 + a i , p o s + a i ] [1, pos] \bigcup [1 + a_i, pos + a_i] [1,pos][1+ai,pos+ai],显然这个集合中不包含 p o s + 1 pos + 1 pos+1,所以答案就是 p o s + 1 pos + 1 pos+1

  这样做对于每次询问的时间复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的,所以要考虑优化这个做法。

  如果现在的值域是 [ 1 , p o s ] [1, pos] [1,pos],那么现在的答案就应该是 p o s + 1 pos + 1 pos+1。记小于等于 a n s ans ans 的数的和为 r e s res res,如果 r e s ≥ a n s res \geq ans resans,那么就说明一定有没有选过(选一些数的和为 a n s ans ans)的且小于等于 a n s ans ans 的数存在,那么现在令 a n s = r e s + 1 ans = res + 1 ans=res+1。如果不满足 r e s ≥ a n s res \geq ans resans 就说明答案就是 a n s ans ans,直接 b r e a k break break 就好了,注意到 ∑ a i < 1 e 9 \sum a_i < 1e9 ai<1e9,所以可以考虑用主席树维护区间值域和。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 100100
#define INFI 1 << 30

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x;
}

int a[MAXN] = { 0 };
int n = 0; int m = 0;

struct Tnode{
	int dat;
	int ls, rs;
}t[MAXN << 5];
int tot = 0;
int root[MAXN] = { 0 };

inline void up(int p) { t[p].dat = t[t[p].ls].dat + t[t[p].rs].dat; }
int update(int now, int l, int r, int x, int val){
	int p = ++tot; t[p] = t[now];
	if(l == r) { t[p].dat += val; return p; }
	int mid = (l + r) >> 1;
	if(x <= mid) t[p].ls = update(t[p].ls, l, mid, x, val);
	else t[p].rs = update(t[p].rs, mid + 1, r, x, val);
	up(p); return p;
}

int query(int p, int q, int l, int r, int ql, int qr){
	if(ql <= l and r <= qr) return t[p].dat - t[q].dat;
	int mid = (l + r) >> 1, ans = 0;
	if(ql <= mid) ans += query(t[p].ls, t[q].ls, l, mid, ql, qr);
	if(qr > mid) ans += query(t[p].rs, t[q].rs, mid + 1, r, ql, qr);
	return ans;
}

int main(){
	n = in;
	for(int i = 1; i <= n; i++) a[i] = in;
	for(int i = 1; i <= n; i++) root[i] = update(root[i - 1], 1, INFI, a[i], a[i]);
	m = in;
	while(m--){
		int l = in, r = in, ans = 1;
		while(true){
			int res = query(root[r], root[l - 1], 1, INFI, 1, ans);
			if(res >= ans) ans = res + 1;
			else break;
		}
		cout << ans << '\n';
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值