1.分树 (tree.cpp)
题目大意:给你一棵结点数为 n n n的树,你要将树划分为几个结点树相同的联通块,求划分方案,答案对 998244353 998244353 998244353取模(两种划分方案不同当且仅当存在至少一个结点被分入了两个不同的连通子树。)
思路:
第一步:
我们先来考虑连通块的大小
d
d
d,显然只有满足
d
∣
n
d|n
d∣n的时候条件才成立。
第二步:
我们再来想想方法数如何计算。我们先看下面的这张图
上面这张图详细阐明了我们的方法。根据上面的方法,我们就可以设计出一下算法:
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∣(ai−ai−1)∣,求出总代价的最小值。
思路:
我们先考虑部分分做法,或许会有其启示。
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)=∣R−B∣ ,其中
∣
∣
||
∣∣为绝对值。
每一种
S
S
S的取法都会产生对应的
f
(
S
)
f(S)
f(S),请你帮助Bob求出所有
f
(
S
)
f(S)
f(S)的和,答案对998244353取模。
思路:
1.30分做法
对于
n
≤
20
n\le20
n≤20我们直接暴力枚举
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)=∣R−B∣。而石子的总数为
X
=
R
+
B
X=R+B
X=R+B,那么
f
(
S
)
=
∣
X
−
2
B
∣
f(S)=|X-2B|
f(S)=∣X−2B∣,假
设所有元素和为
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=1xsi∣x−2i∣。
所以我们的问题就转化为如何统计 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=∑ak≤isi−ak
代码:
#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
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]其实可以合并为
[
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;
}