2021NOIP提高组
T1报数(普及/提高-)
桶排序/筛质数
看到题目我首先的想法是像欧拉筛一样以 O ( n ) O(n) O(n)的时间复杂度将7的倍数以及含有7的数字以及其倍数找出来,并进行标记,接下来将未被标记的数存储在vector数组里,然后可以用二分查找 O ( l o g n ) O(logn) O(logn)的时间复杂度查找vector数组中是否有这个数,如果有则输出它的下一位,否则输出-1。
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+100;
bool s[N];
int ans[N];
int main(){
for(int i=1;i<=1e7+10;i++){
if(s[i]) continue;
int temp=i;
while(temp){
int tmp=temp%10;
if(tmp==7){
for(int j=1;j*i<=N;j++) s[j*i]=1;
break;
}
temp=temp/10;
}
}
int cnt=0;
for(int i=1;i<=N;i++){
if(!s[i]) ans[++cnt]=i;
}
int t;
cin>>t;
while(t--){
int x;
cin>>x;
int idx=lower_bound(ans+1,ans+cnt+1,x)-ans;
if(s[x]) cout<<"-1"<<endl;
else cout<<ans[idx+1]<<endl;
}
}
T2方差(提高+/省选-)
记忆化搜索(50)
计数dp(100)
将题目简化一下,题目要求我们从 0 − m 0-m 0−m这 m + 1 m+1 m+1个数当中选择 n n n个排成一个序列 a a a,并要求这个序列所组成的 S = 2 a 1 + 2 a 2 + ⋯ + 2 a n S=2^{a_1}+2^{a_2}+\dots+2^{a_n} S=2a1+2a2+⋯+2an中一的个数不大于 K K K个。
通过分析我们发现序列 a a a中的数其实就是代表 S S S在位置 a i a_i ai中有一个1,但是因为 a a a中的数可以重复,这就牵扯到了进位的问题。例如:假设 a a a中序列有两个2,那么 S S S的二进制在第2位上就会是0而不是1,因为二进制进位。
进一步分析,因为二进制进位只会从低位到高位,我们从第0位开始考虑:假设 a a a序列中只有一个0,那么S的二进制第 0 0 0位就是1,接下来我们只需考虑 1 − m 1-m 1−m位中,选择n-1个,有 K − 1 K-1 K−1个1的情况,以及0在 a a a这个序列中的位置即可,这就成功的缩小了范围。
但是如果 a a a序列中有多个 0 0 0呢?这就涉及到了进位问题,假设有 k k k个0,那么 S S S中第 0 0 0位就是 k m o d 2 k\ mod\ 2 k mod 2,然后向前进 ⌊ k / 2 ⌋ ⌊k/2⌋ ⌊k/2⌋位,那么我们就要考虑在 1 − m 1-m 1−m位中选择 n − k n-k n−k个,有 K − k m o d 2 K-k\ mod\ 2 K−k mod 2个1的情况,以及 0 0 0在 a a a这个序列中的位置。
化为更一般的情况,设
d
p
[
i
]
[
j
]
[
k
]
[
p
]
dp[i][j][k][p]
dp[i][j][k][p]表示目前已经填了
i
i
i位,序列
a
a
a中的元素已经用了
j
j
j个,现在序列构成的
S
S
S中有
k
k
k个1,并将向下一位进
p
p
p位。因为低位的进位会对高位有影响,而高位的进位对低位没有影响,所以我们这道题的转移方向可以从低位到高位转移,即:
d
p
[
i
]
[
j
]
[
k
]
[
p
]
−
>
d
p
[
i
+
1
]
[
j
+
t
]
[
k
+
t
+
p
&
1
]
[
t
+
p
>
>
1
]
dp[i][j][k][p]->dp[i+1][j+t][k+t+p\&1][t+p>>1]
dp[i][j][k][p]−>dp[i+1][j+t][k+t+p&1][t+p>>1]
其中
t
t
t指的是在
a
a
a序列中有
t
t
t个
i
i
i,因为序列是有序的,所以我们也要考虑
i
i
i在
a
a
a序列中的位置。那么就有下面的式子:
d
p
[
i
+
1
]
[
j
+
t
]
[
k
+
t
+
p
&
1
]
[
t
+
p
>
>
1
]
+
=
C
n
−
j
t
∗
v
[
i
]
[
t
]
∗
d
p
[
i
]
[
j
]
[
k
]
[
p
]
dp[i+1][j+t][k+t+p\&1][t+p>>1]+=C_{n-j}^{t}*v[i][t]*dp[i][j][k][p]
dp[i+1][j+t][k+t+p&1][t+p>>1]+=Cn−jt∗v[i][t]∗dp[i][j][k][p]
值得注意的是,最高位也有可能会进位,所以我们最后在统计答案时,要统计上第
m
+
1
m+1
m+1位上有几个1,如果加上现有的1的个数不超过
K
K
K
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f[105][50][50][50];
int v[105];
ll c[50][50];
ll pv[105][35];
const int mod=998244353;
void init(){
for(int i=0;i<=35;i++){
c[i][0]=1;
for(int j=1;j<=i;j++){
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
}
}
}
ll com(int x){
int res=0;
while(x){
res+=x&1;
x>>=1;
}
return res;
}
int main(){
int n,m,K;
cin>>n>>m>>K;
init();
//cout<<c[5][3]<<endl;
for(int i=0;i<=m;i++){
cin>>v[i];
pv[i][0]=1;
for(int j=1;j<=n;j++){
pv[i][j]=pv[i][j-1]*v[i]%mod;
}
}
f[0][0][0][0]=1;
for(int i=0;i<=m;i++){
for(int j=0;j<=n;j++){
for(int k=0;k<=K;k++){
for(int p=0;p<=(n>>1);p++){
for(int t=0;t<=n-j;t++){
f[i+1][j+t][k+(t+p&1)][t+p>>1]=(f[i+1][j+t][k+(t+p&1)][t+p>>1]+f[i][j][k][p]*pv[i][t]%mod*c[n-j][t]%mod)%mod; //cout<< f[i+1][j+t][k+(t+p&1)][t+p>>1]<<'a'<<endl;
}
}
}
}
}
ll ans=0;
for(int k=0;k<=K;k++){
for(int p=0;p<=n>>1;p++){
if(k+com(p)<=K){
ans=(ans+f[m+1][n][k][p])%mod;
}
}
}
cout<<ans<<endl;
}
后两道,咕咕咕