NOIP2021提高组题目解析

我真的是太菜了,....,随便点开个NOIP提高组的题目就不会做,真的是怀疑当初自己是怎么拿到省一的.....

P7961 [NOIP2021] 数列

先来看一道有意思的计数题,(计数题真的是我的弱项...)
题意大概:给定数列v0,v1,...,vm,从0,1,...,m中选出n个组成序列ai(可以重复选)。要求2a0+2a1+...+2am的二进制下1的个数不大于k,问所有vai的乘积和。
首先我们考虑选择的序列a,因为要考虑到进位的问题,所以我们不妨规定a是不减的。接下来考虑怎么处理这个二进制下1的个数的问题。一个比较好的想法就是将当前的进位我们都停留在当前的i。就是我们把他的进位都转化成我们当前正在处理的数上,这样方便我们量化和处理。根据这些,我们可以大致的设一个状态f[i][j][k][num]表示当前选到了第i个数,数字用到了j,且之前所有小于j的进位相当于k,当前二进制和下的1的个数为num.关于状态转移,我们可以考虑数字j用了多少个,于是就有
f[i][j][k][num]=(Cixf[ix][j1][kpocent(num)+pocnet(numx)][temp]v[j]x),其中pocent(x)表示x的二进制下1的个数,temp/2+x=num.这样就完美的解决了问题。考虑时间复杂度,O(n4m)大概可以的。
注意初始化,和很多的边界问题(毕竟我也卡了很久...,可能是因为我太菜了...呜呜呜!)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=35,M=105,P=998244353;
int n,m,K,v[M],po[N];
ll f[N][M][N][N],jc[N],jc_inv[N];
//f[i][j][k][num]表示前i个,选到了数字j,
//当前累计到数字j的进位为k,当前的二进制数中1的个数为num的乘积和。
inline ll power(ll x,ll y)
{
    ll ans=1;
    while(y)
    {
        if(y&1) ans=ans*x%P;
        y>>=1;
        x=x*x%P;
    }
    return ans%P;
}
inline void prework()
{
    int n=34;
    jc[0]=1;jc_inv[0]=1;
    for(int i=1;i<=n;++i) jc[i]=jc[i-1]*i%P;
    jc_inv[n]=power(jc[n],P-2); 
    for(int i=n-1;i>=1;--i) jc_inv[i]=jc_inv[i+1]*(i+1)%P;
    for(int i=0;i<=n;++i)
    {
        int ct=0,x=i; 
        while(x)
        {
            if(x&1) ct++;
            x>>=1;
        }
        po[i]=ct;
    }
} 

inline ll C(int n,int m)
{
    return jc[n]*jc_inv[n-m]%P*jc_inv[m]%P;
}

int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d%d%d",&n,&m,&K);
    for(int i=0;i<=m;++i) scanf("%d",&v[i]);
    prework();
//    for(int i=1;i<=n;++i)
//        f[i][0][i][po[i]]=power(v[0],i);
    for(int i=1;i<=n;++i)//前i个数 
        for(int j=0;j<=m;++j)//用到了数字j 
        {
            for(int k=0;k<=i;++k)//累计到当前数字j的进位 
                for(int l=po[k];l<=n;++l)//二进制下1的个数. 
                {
                    for(int x=0;x<=k;++x)//枚举当前数字j的个数。 
                    {
                        if(i==x&&k==i&&l==po[i]) f[i][j][k][l]=power(v[j],x);
                        else
                        {
                            if(l-po[k]+po[k-x]<=0) continue;
                            int t1=(k-x)*2;
                            if(t1<=i-x&&j>=1) f[i][j][k][l]=(f[i][j][k][l]+C(i,x)*f[i-x][j-1][t1][l-po[k]+po[k-x]]%P*power(v[j],x)%P)%P;
                            if(t1+1<=i-x&&j>=1) f[i][j][k][l]=(f[i][j][k][l]+C(i,x)*f[i-x][j-1][t1+1][l-po[k]+po[k-x]]%P*power(v[j],x)%P)%P;   
                        }
                    }
//                    printf("%d %d %d %d %lld\n",i,j,k,l,f[i][j][k][l]);
                }
        }
    ll ans=0;
    for(int i=0;i<=n;++i)  
        for(int j=0;j<=K;++j) 
            ans=(ans+f[n][m][i][j])%P;
    printf("%lld",ans);                        
    return 0;    
}

P7962 [NOIP2021] 方差

接下来看这个题,也是很有趣的一道题。
简化题意:给定数列ai,你可以进行若干次操作,每次操作都选定一个i,1<i<n,然后将ai+1+ai1ai替换掉ai。最后要求整个数列的方差最小,输出这个最小的方差乘n2的结果。
首先需要将答案变一下形式,通过简单的变换可以得知最后要求的答案为ans=n×i=1nai2sum2,简单的观察发现没什么性质,就先放到这。
之后观察这个替换到底有什么性质,ai替换为ai+1+ai1ai,其实和容易发现是ai两边的数与ai的关系,考虑我们学过什么也是这个样子的?差分嘛!我们观察他们的差分有什么变化,ai1,ai,ai+1的差分为ai1,aiai1,ai+1aiai1,ai+1+ai1ai,ai+1的差分为ai1,ai+1ai,aiai1,惊奇的发现数值竟然没有发生变化,只是位置发生了变化。说明这些操作是能将差分序列就行位置上的重排。那接下来考虑差分上怎样的位置会使得上面式子的答案最小。
这个时候就有一些考试技巧在里面了,如果实在考场上的话,完全可以观察一下样例的性质,还可以打表,多找一下规律,这样的话,可以节省思维难度。
接下来继续推上面的式子,由于是求方差,所以我们可以将整个数列中的每一个数都减去某个数,最后的结果是不变的。那我们就将上面的式子继续修改,尽可能的想差分靠近。我们先假设di=ai+1ai
ans=n×i=1nai2sum2
=n×i=1n(aia1)2(i=1naia1)2
=n×i=1n1(j=1idj)2(i=1n1j=1idj)2
=n×i=1n1j=1ik=1idjdk(i=1n1(ni)di)2
=n×j=1n1k=1n1djdk(nmax(j,x))j=1n1k=1n1djdk(nj)(nk)
=j=1n1k=1n1djdk((j+kmax[j,k])njk)
=j=1n1k=1n1djdk(min[j,k]njk)
=i=1n1idi2(ni)+2i=1n1j=i+1n1didji(ni)
观察这个式子,发现前一半的式子值是固定的,我们想要后一半的式子尽可能的小,发现这个式子中出现次数最多的乘积是中间项。所以答案最小的话,差分序列应该是呈现一个先减后增的形式。考虑先将所有的差分先排序,之后依次考虑它查到当前序列的前段还是后端,采用dp的形式,推导后的式子太麻烦,还是回到最初的式子,ans=n×i=1nai2sum2。发现这种形式可以用dp很方便的处理,看看我们需要什么量,发现还需要保存当前所有ai的和,于是我们设f[i][x]表示放到了第i个差分,当前所有ai的和是x的所有ai的平方的和的最大值。至于转移就很正常了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e4+10,M=6e6+10;
const ll qwq=0x3f3f3f3f3f3f3f3f;
int n,a[N],d[N],s[N],mx;
ll f[2][M];
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    for(int i=2;i<=n;++i) a[i]-=a[1];
    a[1]=0;mx=a[n];
    for(int i=2;i<=n;++i) d[i-1]=a[i]-a[i-1];
    sort(d+1,d+n);
    for(int i=1;i<n;++i)  s[i]=s[i-1]+d[i];
    memset(f,0x3f,sizeof(f));
    int u=0;
    f[u][0]=0;
    for(int i=1;i<n;++i)
    {
        if(d[i]==0) continue;
        u=u^1;
        for(int j=0;j<=mx*i;++j) f[u][j]=qwq;
        for(int j=0;j<=mx*(i-1);++j)
        {
            if(f[u^1][j]==qwq) continue;
            f[u][d[i]*i+j]=min(f[u][d[i]*i+j],f[u^1][j]+(ll)d[i]*d[i]*i+(ll)2*j*d[i]);
            f[u][j+s[i]]=min(f[u][j+s[i]],f[u^1][j]+(ll)s[i]*s[i]);
        }
    }
    ll ans=1e18;
    for(int i=0;i<=mx*(n-1);++i) 
        if(f[u][i]!=qwq) ans=min(ans,f[u][i]*n-(ll)i*i); 
    printf("%lld\n",ans);
    return 0;
}
posted @   逆天峰  阅读(1128)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//
点击右上角即可分享
微信分享提示