NOIP2021 部分题目整理
(在 NOIP2022 前夕整理 NOIP2021。)
(真的太逊了。)
P7960 报数 Number
解法
由于题目中的因数的限制,考虑埃式筛。在埃式筛中如果遇到某个尚未被标记为非法的数时,如果该数本身包含 \(7\),则需要对其所有倍数标记为非法。由于每个数最多不会被标记超过 \(5\) 次(\(7\times 17\times 27\times 37\times 47\times 57>3\times 10^8\)),故整个过程时间复杂度很小,可以说是小常数的 \(O(x\log x)\)(时间复杂度为 \(O(x\log x)\) 的证明)。
代码
点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=10001000;
inline bool p(int x){
while(x){
if(x%10==7) return 1;
x/=10;
}
return 0;
}
int n,i,j,l;
int nxt[maxn];
int main(){
for(i=1;i<maxn;++i){
if(nxt[i]) continue;
if(p(i)) for(j=i;j<maxn;j+=i) nxt[j]=-1;
if(!nxt[i]) l=nxt[l]=i;
}
scanf("%d",&n);
while(n--){
scanf("%d",&l);
printf("%d\n",nxt[l]);
}
return 0;
}
P7961 数列 Sequence
解法
直接状压 dp(\(S\le 30\times 2^{12}\)),可得 \(50\) 分。
考虑从 \(v_0\) 考虑到 \(v_m\),将 \(2^0\sim 2^m\) 依次加入 \(S\)。在考虑到 \(v_i\) 时,我们就只需要关心 \(S\bmod 2^i\) 有多少个 \(1\),与 \(\lfloor S/2^i \rfloor\) 的值(涉及到进位等内容)。
注意 \(\{a_i\}\) 不一定是非降的。
推式子可得如下结论:
设 \(dp_{i,j,k,p}\) 表示考虑到 \(v_i\) (可以把这一维用滚动数组的方式优化成 \(2\)),使用了 \(j\) 个数,\(S\!\! \mod 2^i\) 有 \(k\) 个 \(1\),\(\lfloor S/2^i \rfloor\) 为 \(p\) 的 \(\sum\prod_{q=1}^i v_{a_q}\),则转移有
时间复杂度:\(\mathcal{O}(n^4m)\),可以通过此题。
代码
醒醒大清无了
点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=40;
const int maxm=100;
const ll md=998244353;
int n,m,k,i,j,p,q,r,s;
bool x,y=1;
ll v,cf,tmp,C[maxn][maxn];
ll dp[2][maxn][maxm][maxn];
inline void Add(ll &_x,const ll _y){
_x+=_y;
if(_x>=md) _x%=md;
}
int main(){
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
C[0][0]=1;
for(i=1;i<maxn;++i){
for(j=0;j<=i;++j){
Add(C[j][i],C[j][i-1]+C[j-1][i-1]);
}
}
scanf("%d%d%d",&n,&m,&k);
dp[0][0][0][0]=1;
for(i=0;i<=m;++i){
scanf("%lld",&v);
for(r=0;r<=n;++r){
cf=1;
for(p=0;p<=r;++p){
for(j=0;j<maxm;++j){
s=j&1;
for(q=0;q<=k;++q){
tmp=dp[x][r-p][j][q]*cf;
if(tmp>=md) tmp%=md;
Add(dp[y][r][(j>>1)+p][s],tmp*C[p][r]);
if((++s)>k) break;
}
}
cf*=v;
if(cf>=md) cf%=md;
}
}
memset(dp[x],0,sizeof(dp[x]));
swap(x,y);
}
tmp=0;
for(i=0;i<maxm;++i){
if(!i) s=0;
else s=__builtin_popcount(i);
for(j=0;j<=k;++j){
if(s>k) break;
Add(tmp,dp[x][n][i][j]);
++s;
}
}
printf("%lld",tmp);
return 0;
}
P7962 方差 Variance
解法
考虑在将 \(a_i\) 变为 \(a_{i-1}+a_{i+1}-a_i\) 之后的影响:\((a_{i-1}+a_{i+1}-a_i)-a_{i-1}=a_{i+1}-a_i,a_{i+1}-(a_{i-1}+a_{i+1}-a_i)=a_i-a_{i-1}\),相当于交换了 \(i+1\) 和 \(i\) 处的差分。记 \(d_i=a_i-a_{i-1}\),则 \(d_2\sim d_n\) 在这些操作后可以任意排列。
此时对于某个平均数 \(S\),如果某两个数 \(a_i,a_{i+1}\) 均小于 \(S\) 且 \(d_{i+1}>d_i\),则将 \(d_i,d_{i+1}\) 交换后 \(a_i\) 值会变大但不超过 \(a_{i+1}\),\(a_i\) 将会更接近 \(S\),方差会更小。故最后的 \(d\) 一定会呈现单谷。
定义 \(S=\sum_{j=1}^n a_j\),则原式可以简化如下:
考虑从单谷部分开始向两边 dp,设 \(dp_{i,j}\) 为考虑了前 \(i\) 个差分,且当前 \(a\) 值之和为 \(j\) 时所有 \(a\) 的平方和最小值。此时对每个 \(a\) 值均减去 \(a_1\) 不会对答案造成影响,可以设 \(a_1=0\),也就是初值为 \(dp_{1,0}=0,\forall i\ne 0,dp_{1,i}=+\infty\)。转移时,如果新的 \(d_i\) 放在最左端,则其将其他每个 \(a\) 值均增加了 \(d_i\),新的 \(a\) 值也是 \(d_i\);如果新的 \(d_i\) 放在最右端,则这个新增的 \(a\) 值为 \(\sum_{j=2}^i d_j\),而其他 \(a\) 值不会被影响。设 \(s_i=\sum_{j=2}^i d_j\),则转移有:
第一维可以使用背包的类似优化方式优化掉。考虑 \(d_i=0\) 时一定有 \(s_i=0\),转移只会有 \(dp_{i,j}=\min(dp_{i,j},dp_{i-1,j})\),则可以把 \(d_i=0\) 的部分跳过,则最后有用的 \(d\) 值只会有 \(\min(n,a)\) 个,故优化后的时间复杂度为 \(O(\min(n,a)na)\)。
代码
正解甚至比 8 pts 暴力短()
点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=10010;
const int maxv=500010;
int n,i,j,s,v,d[maxn];
ll a=1e14,dp[maxv];
inline void cmin(ll &x,ll y){if(y<x) x=y;}
int main(){
freopen("variance.in","r",stdin);
freopen("variance.out","w",stdout);
scanf("%d",&n);
for(i=1;i<=n;++i) scanf("%d",d+i);
for(i=n;i;--i) d[i]-=d[i-1];
for(j=0;j<maxv;++j) dp[j]=1e14;
sort(d+2,d+n+1); dp[0]=0;
if(!d[n]){printf("0\n");return 0;}
for(i=2;i<=n;++i){
if(!(s+=d[i])) continue; v=(i-1)*d[i];
for(j=maxv-1;~j;--j){
if(j<maxv-v) cmin(dp[j+v],dp[j]+1LL*d[i]*v+2*d[i]*j);
if(j<maxv-s) cmin(dp[j+s],dp[j]+s*s); dp[j]=1e14;
}
}
for(j=1;j<maxv;++j) a=min(a,dp[j]*n-1LL*j*j);
printf("%lld",a);
return 0;
}
P7963 棋局 Chess
解法(暂缺)
代码(暂缺)
点此查看代码
本文来自博客园,作者:Fran-Cen,转载请注明原文链接:https://www.cnblogs.com/Fran-CENSORED-Cwoi/p/16911654.html