NOIP提高组模拟赛14

A. 异或

这种题就是打表,什么性质不性质的不重要

不知道我打表出的式子怎么解释,,

粘一下题解做法吧

"考虑每一位的贡献,从低到高的第\(i\)位会每隔\(2^i\)个数变化一次,于是第$$i位对答案的贡献就是\(\lfloor\frac{i}{2^i}\rfloor\)
把每一位的贡献加起来即可。"

我的代码

code
#include <cstdio>
using namespace std;
unsigned long long n;
int main(){
    scanf("%llu",&n);
    unsigned long long base=2;
    unsigned long long ans=0;
    while(n){
        if(n&1)ans+=base-1;
        base<<=1;
        n>>=1;
    }
    printf("%llu\n",ans+n);
    return 0;
}

B. 赌神

考场理解错了题意,导致送的分都没拿到

考虑\(n=2\)的情况

\(dp[i][j]\)表示第一种颜色的球剩\(i\)个,第二种颜色的球剩\(j\)个对手中筹码的最大贡献,也可以说是表示你当前有1个筹码,箱子里两种颜色的球分别有\(i,j\)个,你最终能获得多少筹码,这个“1”是单位“1”

由于幕后黑手肯定会按你得筹码最少的情况掉球,所以在最优策略下,不同的掉球情况下最终所能获得的筹码数应该相同

假设你在第一种颜色上押\(x\)个筹码,在第二种颜色上押\(y\)个筹码,有\(x\times dp[i-1][j]=y\times dp[i][j-1]\)又有\(x+y=1\)可以解得\(x\)

容易发现全押一定比不押优(\(dp[i][j]>=1\)),于是有\(dp[i][j]=\frac{2dp[i-1][j]dp[i][j-1]}{dp[i-1][j]+dp[i][j-1]}\)

发现那个系数\(2\)看着不好看,而且可以最后再乘上

所以设\(f[i][j]\times 2^{i+j}=dp[i][j]\)

那么有\(\frac{1}{f[i][j]}=\frac{1}{f[i-1][j]}+\frac{1}{f[i][j-1]}\)

好眼熟,这不是二维图中只能向右上走的方案数吗,直接\((^{i+j}_{\;\;i})\)

答案就是\(\frac{2^{i+j}}{(^{i+j}_{\;\;i})}\)

结论可以扩展到多维情况

code
#include <cstring>
#include <cstdio>
using namespace std;
const int mod=998244353;
const int maxn=1000005;
int n,x[maxn+15];
long long jc[maxn+15],inv[maxn+15];
long long qpow(long long x,long long y){
    long long ans=1;
    while(y){
        if(y&1)ans=ans*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return ans;
}
void ycl(){
    jc[0]=1;jc[1]=1;inv[0]=1;
    for(int i=2;i<=maxn;++i)jc[i]=jc[i-1]*i%mod;
    inv[maxn]=qpow(jc[maxn],mod-2);
    for(int i=maxn-1;i>=1;--i)inv[i]=inv[i+1]*(i+1)%mod;
}

long long get_C(int n,int m){return jc[n]*inv[m]%mod*inv[n-m]%mod;}


int main(){
    scanf("%d",&n);ycl();
    for(int i=1;i<=n;++i)scanf("%d",&x[i]);
    long long sum=0;
    for(int i=1;i<=n;++i)sum+=x[i];
    long long ans = qpow(n,sum);
    long long in=1;
    for(int i=1;i<=n;++i){
      in=in*get_C(sum,x[i])%mod;
      sum-=x[i];
    }
    printf("%lld\n",ans*qpow(in,mod-2)%mod);
    return 0;
}

C. 路径

题解不做人,给了个错误式子。。

正解确实要斯特林数,但是还需要换根\(DP\)

\(x^k=\sum_{i=1}^k \begin{Bmatrix}k\\ i\end{Bmatrix}\times x^{\underline i}\)

\((x+1)^{\underline i}=i\times x^{\underline{i-1}}+x^{\underline i}\)

\(f[i][j]\)表示\(i\)子树内到\(i\)距离的\(j\)次下降幂之和,\(g[i][j]\)表示\(i\)子树内到\(i\)的父亲距离的\(j\)次下降幂之和

\(f_{i,j}=\sum_{u\in son_i}g_{u,j}\)

\(g_{i,j}=j\times f_{i,j-1}+f_{i,j}\)

看码注释吧。。

code



#include <cstring>
#include <cstdio>
using namespace std;
const int maxn=1000005;
const int maxk=105;
const int mod=998244353;
const int inv_2=499122177;//2在%mod意义下的逆元
struct edge{
   int net,to;
}e[maxn<<1|1];
int head[maxn],tot,n,k;
void add(int u,int v){
   e[++tot].net=head[u];
   head[u]=tot;
   e[tot].to=v;
}

int s[maxk][maxk];//斯特林数
int f[maxn][maxk];//f[x][i]x子树内所有点到x的距离的i次下降幂之和
int g[maxn][maxk];//g[x][i]x子树内所有点到x的父亲距离的i次下降幂之和
void pre(){
    s[1][1]=1;
    for(int i=2;i<=k;++i)
      for(int j=1;j<=i;++j)
        s[i][j]=(s[i-1][j-1]+1ll*s[i-1][j]*j%mod)%mod;
    //第二类斯特林数递推,s(n,m)将n个元素划分为m个非空集合的方案数
    //s(n,m)=s(n-1,m-1)+s(n-1,m)*m,前n-1个元素划分为m-1个集合,当前元素只能在第m个集合,前n-1个元素分为m个集合,当前元素可以在任意集合
}

void dfs(int x,int fa){
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;
        if(v==fa)continue;
        dfs(v,x);
        for(int i=0;i<=k;++i){
            if(!g[v][i])break;
            f[x][i]=(f[x][i]+g[v][i])%mod;
        }
    }
    ++f[x][0];
    for(int i=k;i;--i)
      g[x][i]=(1ll*i*f[x][i-1]%mod+f[x][i])%mod;//x+1的i次下降幂==(x的i-1次下降幂乘以i )+x的i次下降幂
    g[x][0]=f[x][0];
}
int tmp[maxk],sum[maxk];
//换根DP,统计所有点到当前根的贡献,每条路径会被计算两次,最后需要乘2的逆元
void dfs_get(int x,int fa){
    for(int i=0;i<=k;++i){
        if(!f[x][i])break;
        sum[i]=(sum[i]+f[x][i])%mod;
    }
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;
        if(v==fa)continue;
        for(int j=0;j<=k;++j)tmp[j]=(f[x][j]-g[v][j]+mod)%mod;
        for(int j=k;j;--j)tmp[j]=(1ll*tmp[j-1]*j%mod+tmp[j])%mod;
        for(int j=0;j<=k;++j)f[v][j]=(f[v][j]+tmp[j])%mod;
        dfs_get(v,x);
    }
}

int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<n;++i){int u,v;scanf("%d%d",&u,&v);add(u,v);add(v,u);}
    pre();
    dfs(1,0);
    dfs_get(1,0);
    int ans=0;
    for(int i=0;i<=k;++i)ans=(ans+1ll*s[k][i]*sum[i]%mod)%mod;
    ans=1ll*ans*inv_2%mod;
    printf("%d\n",ans);
    return 0;
}

D. 树

神仙分块题。

首先\(dfs\)是肯定的,转化为区间问题

暴力分比较好拿,对每个深度的点建线段树即可。(实际上\(vector\)+二分即可)

正解需要离线

首先所有修改可以转化为修改某个区间所有深度\(dep\%x=y^`\)的点

对于模数\(x<=\sqrt n\)\(x>\sqrt n\)的修改分开处理

先说\(x<\sqrt n\)

首先枚举\(x\)\(\sqrt n\)

我们需要保证的最终复杂度是\(O(q\sqrt n)\)

注意到询问操作需要进行\(q\sqrt n\)次,那么要保证每次查询是\(o(1)\)

而修改操作需要\(1\)次,分块处理复杂度就可以保证了

\(f[pos][res]\)记录第\(pos\)块余数为\(res\)的点需要加上的权值,这样对不同余数分开处理

不是整块怎么办?点的深度是确定的,所以只用开一维记录,修改时候判一下该点是否为需要修改的点即可

然后\(x>\sqrt n\)

你会发现每次修改最多\(\sqrt n\)个深度,考虑最开始暴力的想法,对每个深度分别处理

为了保证复杂度为\(O(q\sqrt n)\)那么修改操作必须\(O(1)\)完成,那么自然需要使用差分

查询时候搞一下前缀和(分块实现),这样总复杂度保证\(O(q\sqrt n)\)

但是发现直接这样搞数组开不下,那么每次处理\(\sqrt n\)个深度的操作,分\(\sqrt n\)次进行,

分块数组一次只能处理一个深度,所以把有关这\(\sqrt n\)个深度的操作分开存一下,一次处理一个深度

注意不能直接一个一个深度处理,那样复杂度是\(O(nq)\)的(因为每个操作被扫到了\(n\)次)(虽然数据可以水过去

code


#include <cstring>
#include <vector>
#include <cstdio>
#include <cmath>
using namespace std;
int min(int x,int y){return x<y?x:y;}
const int maxn=300005;
const int gm=605;
struct edge{
   int net,to,val;
}e[maxn<<1|1];
int head[maxn],tot;
void add(int u,int v){
   e[++tot].net=head[u];
   head[u]=tot;
   e[tot].to=v;
}
int n,len,q;
struct node{
    int dep,fa,dfsl,dfsr;
}d[maxn];
int id[maxn];
int tmp,maxdep;
void dfs(int x){
    d[x].dfsl=++tmp;id[tmp]=x;
    for(int i=head[x];i;i=e[i].net){
        int v=e[i].to;
        if(v==d[x].fa)continue;
        d[v].fa=x;d[v].dep=d[x].dep+1;
        if (d[v].dep>maxdep)maxdep=d[v].dep;
        dfs(v);
    }
    d[x].dfsr=tmp;
}
struct ask{
    int l,r,x,y,z,op,pos;
}ak[maxn];

int fk[gm][gm],yk[maxn],pos[maxn],ans[maxn];
void work1(){
    for(int mod=1;mod<=len;++mod){
        for(int i=1;i<=q;++i){
            if(ak[i].op==2){
              ans[i]+=fk[pos[ak[i].pos]][d[id[ak[i].pos]].dep%mod]+yk[ak[i].pos];
              continue;
            }
            if(ak[i].x!=mod)continue;
            if(pos[ak[i].l]==pos[ak[i].r]){
                for(int l=ak[i].l;l<=ak[i].r;++l)
                    if(ak[i].y==d[id[l]].dep%mod)yk[l]+=ak[i].z;
            }else{
                int ls=pos[ak[i].l]*len;
                for(int l=ak[i].l;l<=ls;++l)
                    if(ak[i].y==d[id[l]].dep%mod)yk[l]+=ak[i].z;
                ls=pos[ak[i].r]*len-len+1;
                for(int l=ls;l<=ak[i].r;++l)
                    if(ak[i].y==d[id[l]].dep%mod)yk[l]+=ak[i].z;
                for(int l=pos[ak[i].l]+1;l<pos[ak[i].r];++l)
                  fk[l][ak[i].y]+=ak[i].z;
            }   
        }
        for(int i=1;i<=q;++i){
            if(ak[i].op==2||ak[i].x!=mod)continue;
            if(pos[ak[i].l]==pos[ak[i].r]){
                for(int l=ak[i].l;l<=ak[i].r;++l)
                    if(ak[i].y==d[id[l]].dep%mod)yk[l]-=ak[i].z;
            }else{
                int ls=pos[ak[i].l]*len;
                for(int l=ak[i].l;l<=ls;++l) if(ak[i].y==d[id[l]].dep%mod) yk[l]-=ak[i].z;
                ls=pos[ak[i].r]*len-len+1;
                for(int l=ls;l<=ak[i].r;++l) if(ak[i].y==d[id[l]].dep%mod) yk[l]-=ak[i].z;
                for(int l=pos[ak[i].l]+1;l<pos[ak[i].r];++l) fk[l][ak[i].y]-=ak[i].z;
            }   
        }
    }
}
int cf[maxn],ck[gm];
struct ll{
    bool op;
    int l,r,z;
    ll(){}
    ll(int _l,int _r,int _z){
        op=1;
        l=_l;
        r=_r;
        z=_z;
    }
    ll(int pos,int id){
        op=0;
        l=pos;
        r=id;
    }
};
vector<ll>v[gm];

void add(int l,int r,int v){
    cf[l]+=v;cf[r+1]-=v;
    ck[pos[l]]+=v;ck[pos[r+1]]-=v;
}
int query(int posi){
    int la=0;
    for(int l=1;l<pos[posi];++l)la+=ck[l];
    for(int l=pos[posi]*len-len+1;l<=posi;++l)la+=cf[l];
    return la;
}

void work2(){
    for(int dl=1;dl<=maxdep;dl+=len){
        int dr=min(maxdep,dl+len-1);
        for(int i=1;i<=q;++i){
            if(ak[i].op==1){
                if(ak[i].x<=len)continue;
                int kl=ceil((1.0*dl-ak[i].y)/ak[i].x);
                int kr=floor((1.0*dr-ak[i].y)/ak[i].x);
                for(int k=kl;k<=kr;++k){
                    int dep=ak[i].y+ak[i].x*k;
                    v[dep-dl].push_back(ll(ak[i].l,ak[i].r,ak[i].z));
                }
            }else{
                int dep=d[id[ak[i].pos]].dep;
                if(dep<dl||dep>dr)continue;
                v[dep-dl].push_back(ll(ak[i].pos,i));
            }
        }
        for(int i=dl;i<=dr;++i){
            int s=v[i-dl].size();
            for(int j=0;j<s;++j)
                if(v[i-dl][j].op)add(v[i-dl][j].l,v[i-dl][j].r,v[i-dl][j].z);
                else ans[v[i-dl][j].r]+=query(v[i-dl][j].l);
            for(int j=0;j<s;++j)
                if(v[i-dl][j].op)add(v[i-dl][j].l,v[i-dl][j].r,-v[i-dl][j].z);
            v[i-dl].clear();
        }
    }
}
int main(){
    scanf("%d%d",&n,&q);
    len=sqrt(n);for(int i=1;i<=n;++i)pos[i]=(i+len-1)/len;
    for(int i=1;i<n;++i){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    d[1].dep=1;dfs(1);
    for(int i=1;i<=q;++i){
        scanf("%d",&ak[i].op);
        if(ak[i].op&1){
            int v,x,y,z;
            scanf("%d%d%d%d",&v,&x,&y,&z);
            ak[i].l=d[v].dfsl;
            ak[i].r=d[v].dfsr;
            ak[i].z=z;ak[i].x=x;
            ak[i].y=(d[v].dep+y)%x;
            
        }else{
            int v;scanf("%d",&v);
            ak[i].pos=d[v].dfsl;
        }
    }
    work1();
    work2();
    for(int i=1;i<=q;++i)
        if((ak[i].op&1)==0)
            printf("%d\n",ans[i]);
    return 0;
}
posted @ 2022-02-22 12:12  Chen_jr  阅读(48)  评论(0编辑  收藏  举报