7月22日模拟赛

1.分树 (tree.cpp)

 题目大意:给你一棵结点数为 n n n的树,你要将树划分为几个结点树相同的联通块,求划分方案,答案对 998244353 998244353 998244353取模(两种划分方案不同当且仅当存在至少一个结点被分入了两个不同的连通子树。

 思路:
第一步:
 我们先来考虑连通块的大小 d d d,显然只有满足 d ∣ n d|n dn的时候条件才成立。
第二步:
 我们再来想想方法数如何计算。我们先看下面的这张图
在这里插入图片描述
 上面这张图详细阐明了我们的方法。根据上面的方法,我们就可以设计出一下算法:
1.枚举n的因数,但这里要注意要从1开始一直枚举到n,而不是根号n,因为如果只枚举到根号n是无法统计完划分方案的。
2.遍历每一个点。
 这样的话,时间复杂度就是 O ( n n ) O(n\sqrt n) O(nn )

 代码:

#include<bits/stdc++.h>
#define in read()
#define MAXN 1000050
#define MAXM 2*MAXN
using namespace std;
int n;
int nex[MAXM],first[MAXM],to[MAXM],tot=0,dat[MAXM];
int bucket[MAXN],ans=0;; 
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0' or c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' and c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
	return x*f; 
}
inline void addedge(int u,int v){
	nex[++tot]=first[u];
	first[u]=tot;
	to[tot]=v;
}
void dfs(int u,int fa){
	for(int e=first[u];e;e=nex[e]){
		int v=to[e];
		if(v==fa)continue;
		dfs(v,u);
		dat[u]+=dat[v];
	}
	bucket[dat[u]]++;//用桶的思想存储
}
int main(){
//	freopen("tree.in","r",stdin);
//	freopen("tree.out","w",stdout);
	n=in;
	for(int i=1;i<=n-1;i++){
		int u=in,v=in;
		addedge(u,v);
		addedge(v,u);
	}
	for(int i=1;i<=n;i++)dat[i]=1;
	dfs(1,0);
	for(int d=1;d<=n;d++){
		if(n%d==0){
			int cnt=0;
			for(int i=1;i<=n/d;i++)cnt+=bucket[i*d];
			if(cnt==n/d)ans++;//判断是否划分成n/d个连通块。 
		}
	}
	cout<<ans<<'\n';
	return 0;
}

2.序列 (seq.cpp)

 题目大意:有一个由正整数组成的序列,我们可以对于每一个 a i a_i ai加上一个非负整数 x i x_i xi,代价为 x i 2 x_i^{2} xi2。最后总代价还要在加上额外花费 ∑ i = 2 n ∣ ( a i − a i − 1 ) ∣ \sum_{i=2}^{n} |(a_i-a_{i-1})| i=2n(aiai1),求出总代价的最小值。

 思路:
 我们先考虑部分分做法,或许会有其启示。
70分思路:(我的思路也是这样的,只是写挂了
在这里插入图片描述
100分思路
在这里插入图片描述

3.石子染色 (color.cpp)

 题目大意:Bob将 X X X个石子分为 n n n堆,每一堆有 A i A_i Ai个。Bob会选择数列 的一个子序列 1 , 2 , 3 , . . . , n 1,2,3,...,n 1,2,3,...,n(可以非连续) S S S ,对于每个 i i i,如果 i i i S S S内,Bob会将第 i i i堆染为红色,反之染为蓝色。令红色的石子数为 R R R,蓝色的石子数为 B B B,Bob定义 f ( S ) = ∣ R − B ∣ f(S)=|R-B| f(S)=RB ,其中 ∣ ∣ || ∣∣为绝对值。
每一种 S S S的取法都会产生对应的 f ( S ) f(S) f(S),请你帮助Bob求出所有 f ( S ) f(S) f(S)的和,答案对998244353取模。

 思路:
1.30分做法
 对于 n ≤ 20 n\le20 n20我们直接暴力枚举 S S S,再统计f(S),时间复杂度是 O ( n 2 n ) O(n2^n) O(n2n)

#include<bits/stdc++.h>
#define in read()
#define MAXN 2050
using namespace std;
int T,x,n,a[MAXN],q[MAXN];
int f[MAXN];
long long ans=0; 

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

void add(int cnt){
	long long R=0,B=0;
	for(int i=1;i<=cnt;i++)if(q[i])R+=a[q[i]];
	B=x-R;ans+=abs(R-B);
}

void dfs(int index,int start,int cnt){
	if(index>cnt)add(cnt);
	else{
		for(int i=start;i<=n;i++){
			q[index]=i;
			dfs(index+1,i+1,cnt);
			q[index]=0;
		}
	}
}


int main(){
	T=in;
	for(int cas=1;cas<=T;cas++){
		x=in,n=in;memset(a,0,sizeof(a));ans=0;
		for(int i=1;i<=n;i++)a[i]=in;
		for(int j=1;j<=n;j++){dfs(1,1,j);}
		cout<<ans+x<<'\n';
	}
	return 0;
}

2.100分做法
 取走一个 S S S后我们知道 f ( S ) = ∣ R − B ∣ f(S)=|R-B| f(S)=RB。而石子的总数为 X = R + B X=R+B X=R+B,那么 f ( S ) = ∣ X − 2 B ∣ f(S)=|X-2B| f(S)=X2B,假
设所有元素和为 i i i S S S s i s_i si个,那么最终答案即为 ∑ i = 1 x s i ∣ x − 2 i ∣ \sum_{i=1}^{x} s_i|x-2i| i=1xsix2i

 所以我们的问题就转化为如何统计 s i s_i si,用背包的思想处理即可,转移方程式 s i = ∑ a k ≤ i s i − a k s_i=\sum_{a_k \le i} s_{i-a_k} si=akisiak

 代码:

#include<bits/stdc++.h>
#define in read()
#define MAXN 2005
#define Mod 998244353
using namespace std;
int T,a[MAXN],s[MAXN];
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0' or c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' and c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
	return x*f;
}
int main(){
	T=in;
	for(int cas=1;cas<=T;cas++){
		memset(s,0,sizeof(s));s[0]=1;//
		int X=in,n=in;
		for(int i=1;i<=n;i++){
			a[i]=in;
			for(int j=X;j>=a[i];j--)
			s[j]=(s[j]+s[j-a[i]])%Mod;
		}
		long long ans=0;
		for(int i=1;i<=X;i++)
		ans=(ans+s[i]*abs((long long)X-2*i)%Mod)%Mod;
		cout<<ans+X<<'\n'; 
	} 
	return 0;
}

4.求和 (mex.cpp)

 题目大意:有一个长度为 m m m的正整数序列,每次询问其子区间 [ l , r ] [l,r] [l,r],再在这个子区间中任意选取若干个数求和(只选 1 1 1个也可)。求出这个区间不能表示的最小的非负整数。
 正解是可持久化线段树维护区间和。但是我不会。所以我来写一下部分分写法。

1.40分解法
 我们对选取区间 [ l , r ] [l,r] [l,r]进行一个升序排序,排序后的数中如果第一个数不等于 1 1 1,那么直接输出 1 1 1即可。如果第一个数等于 1 1 1,我们要进行一个简单的类似递推的思想。

我们令 p o s pos pos为当前扫描到 a [ i ] , i ∈ [ l , r ] a[i],i\in [l,r] a[i],i[l,r]时,已扫描区间所能表示的最大的数,此时数集为 [ 1. p o s ] [1.pos] [1.pos]
 1.如果 a i ≤ p o s + 1 a_i\le pos+1 aipos+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]其实可以合并为 [ 1 , p o s + a i ] [1,pos+a_i] [1,pos+ai]
 2.如果 a i > p o s + 1 a_i\gt 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即可。
 3.对于一个扫描完的区间,但还没有返回值,直接输出 p o s + 1 pos+1 pos+1

#include<bits/stdc++.h>
#define in read()
#define MAXN 100050
using namespace std;
int n,m,a[MAXN],f[MAXN];
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0' or c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' and c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
	return x*f;
}
inline int query(int l,int r){
	for(int i=l;i<=r;i++)f[i]=a[i];
	sort(f+l,f+r+1);
	if(f[l]!=1)return 1;
	int pos=0;
	for(int i=l;i<=r;i++){
		if(f[i]<=pos+1)pos+=f[i];
		else return pos+1; 
	}
	return pos+1;
}
int main(){
	n=in;
	for(int i=1;i<=n;i++)a[i]=in;
	m=in;
	for(int i=1;i<=m;i++){
		int l=in,r=in;
		cout<<query(l,r)<<'\n';
	} 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值