多校冲刺 noip 11.01

多校冲刺 noip 11.01

我还是不可避免的挂分了,\(T1\)日常被卡常

今天做题特别顺手,很容易就切掉了第一题和最后一题

这也依赖于我最近开题策略的转变

在溜达题面的时候就看到最后一题好眼熟就切了

第三题也很好的处理了一下,用一般线段树解决了这类区间问题

但是第二题这种最优策略求期望的题仍然不会

T1 集合均值

这个你发现当每一个数的顺序固定的时候贡献是很容易算出来的

而每一个数在每一个位置的方案数也是很容易算出来的

所以直接切就行了

AC_code
#include<bits/stdc++.h>
using namespace std;
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
const int N=1e5+5;
const int M=2e7+5;
const int mod=998244353;
int ksm(int x,int y){
    int ret=1;
    while(y){
        if(y&1)ret=1ll*ret*x%mod;
        x=1ll*x*x%mod;y>>=1;
    }return ret;
}
int n,m,s[N];
int res,inv[M];
long long sum,p;
int ans;
signed main(){
    freopen("mos.in","r",stdin);
    freopen("mos.out","w",stdout);
    //  cout<<(sizeof(inv)*2>>20)<<endl;
    scanf("%d%d",&n,&m);
    fo(i,1,n)scanf("%d",&s[i]),sum=sum+s[i];
    sum=sum*m%mod;n=n*m;
    inv[0]=inv[1]=1;
    fo(i,2,n+1)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    fu(i,n+1,2)p=(p+1ll*inv[i]*(i-1)%mod);
    ans=1ll*sum*(p%mod)%mod*inv[n]%mod;
    printf("%d",ans);
    return 0;
}

T2 聚烷撑乙二醇

最优策略问题:我只需要知道后面的答案的期望,如果当前的值小于后面的就取后面的值,否则取当前值

所以这个题有了上面那句话的启发就很容易做出来了

直接倒推,只有一个的时候,期望就是中点

下一次就根据上面那句话,我比较现在的值和下一次的期望

小的话就取下面的值,大的话就是当前值的期望

直接转移就行了,我是傻逼,我不会这个题,感谢\(nanfeng-zj\varphi\)

AC_code
#include<bits/stdc++.h>
using namespace std;
#define ld long double
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
const int N=1e6+5;
int n,l[N],r[N];
ld tmp;
signed main(){
    freopen("pag.in","r",stdin);
    freopen("pag.out","w",stdout);
    scanf("%d",&n);
    bool fl1=true,fl2=true;
    fo(i,1,n)scanf("%d%d",&l[i],&r[i]);
    tmp=1.0*(r[n]+l[n])/2;
    fu(i,n-1,1){
        ld tl=l[i],tr=r[i];
        if(tr<=tmp)continue;
        if(tl>=tmp){
            tmp=(tl+tr)/2;
            continue;
        }
        tmp=((tmp-tl)*tmp+(tr-tmp)*(tr+tmp)/2)/(tr-tl);
    }
    printf("%.5Lf",tmp);
    return 0;
}

T3 技术情报局

我在考场上极其勇敢的码了一颗线段树

关于给你一个序列,维护这个序列上所有区间的某个值的问题

我们一般用线段树解决,一般是枚举左(右)端点,用线段树维护另外一个端点

这类问题的贡献一般都是可以拆成一个一个去计算的,这也是可以用线段树维护的原因

所以我们枚举右端点,用线段树维护每一个左端点的值

每加入一个点,在线段树上相应的修改即可

维护两个值,一个是当前区间的乘积,一个是最大值

查询的时候直接乘起来就是答案

80pts
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
namespace GenHelper {
    unsigned z1, z2, z3, z4, b;
    unsigned rand_() {
        b=((z1<<6)^z1)>>13;
        z1=((z1&4294967294U)<<18)^b;
        b=((z2<<2)^z2)>>27;
        z2=((z2&4294967288U)<<2)^b;
        b=((z3<<13)^z3)>>21;
        z3=((z3&4294967280U)<<7)^b;
        b=((z4<<3)^z4)>>12;
        z4=((z4&4294967168U)<<13)^b;
        return (z1^z2^z3^z4);
    }
}
vector<int> get(int n,unsigned s,int l,int r){
    vector<int> a;a.push_back(0);
    using namespace GenHelper;
    z1=s;
    z2=unsigned((~s)^0x233333333U);
    z3=unsigned(s^0x1234598766U);
    z4=(~s)+51;
    for(int i=1;i<=n;i++){
        int x=rand_()&32767;
        int y=rand_()&32767;
        a.push_back(l+(x*32768+y)%(r-l+1));
    }
    return a;
}
const int N=1e7+5;
int n,ll,rr,mod;
unsigned s;
vector<int> xl;
struct XDS{
    #define ls x<<1
    #define rs x<<1|1
    int cf[N*4],tc[N*4],tm[N*4],sm[N*4];
    void pushup(int x){
        cf[x]=(cf[ls]+cf[rs])%mod;
        sm[x]=(sm[ls]+sm[rs])%mod;
        return ;
    }
    void pushdown(int x){
        tc[ls]=tc[ls]*tc[x]%mod;
        tc[rs]=tc[rs]*tc[x]%mod;
        cf[ls]=cf[ls]*tc[x]%mod;
        cf[rs]=cf[rs]*tc[x]%mod;
        sm[ls]=sm[ls]*tc[x]%mod;
        sm[rs]=sm[rs]*tc[x]%mod;
        tc[x]=1;if(tm[x]){
            tm[ls]=tm[rs]=tm[x];
            sm[ls]=cf[ls]*tm[x]%mod;
            sm[rs]=cf[rs]*tm[x]%mod;
            tm[x]=0;
        }
        return ;
    }
    void build(int x,int l,int r){
        tm[x]=0;tc[x]=1;
        if(l==r){
            cf[x]=sm[x]=1;
            return ;
        }
        int mid=l+r>>1;
        build(ls,l,mid);
        build(rs,mid+1,r);
        pushup(x);return ;
    }
    void ins_m(int x,int l,int r,int ql,int qr,int v){
        if(ql<=l&&r<=qr){
            tm[x]=v;sm[x]=cf[x]*v%mod;
            return ;
        }
        if(tc[x]!=1||tm[x])pushdown(x);
        int mid=l+r>>1;
        if(ql<=mid)ins_m(ls,l,mid,ql,qr,v);
        if(qr>mid)ins_m(rs,mid+1,r,ql,qr,v);
        pushup(x);return ;
    }
    void ins_c(int x,int l,int r,int ql,int qr,int v){
        if(ql<=l&&r<=qr){
            cf[x]=cf[x]*v%mod;
            tc[x]=tc[x]*v%mod;
            sm[x]=sm[x]*v%mod;
            return ;
        }
        if(tc[x]!=1||tm[x])pushdown(x);
        int mid=l+r>>1;
        if(ql<=mid)ins_c(ls,l,mid,ql,qr,v);
        if(qr>mid)ins_c(rs,mid+1,r,ql,qr,v);
        pushup(x);return ;
    }
    int query(int x,int l,int r,int ql,int qr){
        if(ql<=l&&r<=qr)return sm[x];
        if(tc[x]!=1||tm[x])pushdown(x);
        int mid=l+r>>1,ret=0;
        if(ql<=mid)ret=(ret+query(ls,l,mid,ql,qr))%mod;
        if(qr>mid)ret=(ret+query(rs,mid+1,r,ql,qr))%mod;
        pushup(x);return ret;
    }
    #undef ls
    #undef rs
}xds;
int sta[N],top,lb[N],rb[N];
int ans;
void spj(){
    int now=ll;
    fo(i,1,n){
        now=now*ll%mod;
        ans=(ans+now*(n-i+1))%mod;
    }
    printf("%lld",ans);
}
signed main(){
    freopen("tio.in","r",stdin);
    freopen("tio.out","w",stdout);
    // cout<<(sizeof(xds.sm)*4+sizeof(sta)*4>>20)<<endl;
    scanf("%lld%u%lld%lld%lld",&n,&s,&ll,&rr,&mod);
    xl=get(n,s,ll,rr);
    if(ll==rr){spj();return 0;}ans=0;
    fo(i,1,n){
        while(top&&xl[sta[top]]<xl[i])rb[sta[top]]=i-1,top--;
        lb[i]=sta[top]+1;
        sta[++top]=i;
    }
    while(top)rb[sta[top]]=n,top--;
    xds.build(1,1,n);
    fo(i,1,n){
        xds.ins_c(1,1,n,1,i,xl[i]);
        xds.ins_m(1,1,n,lb[i],i,xl[i]);
        ans=(ans+xds.query(1,1,n,1,i))%mod;
    }
    printf("%lld",ans);
    return 0;
}

那么正解是啥?

笛卡尔树,我们维护的是最大值

先建树,维护每个点能覆盖的区间的前缀乘积和,后缀乘积和,乘积和

直接子树合并就行了

AC_code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
namespace GenHelper {
    unsigned z1, z2, z3, z4, b;
    unsigned rand_() {
        b=((z1<<6)^z1)>>13;
        z1=((z1&4294967294U)<<18)^b;
        b=((z2<<2)^z2)>>27;
        z2=((z2&4294967288U)<<2)^b;
        b=((z3<<13)^z3)>>21;
        z3=((z3&4294967280U)<<7)^b;
        b=((z4<<3)^z4)>>12;
        z4=((z4&4294967168U)<<13)^b;
        return (z1^z2^z3^z4);
    }
}
vector<int> get(int n,unsigned s,int l,int r){
    vector<int> a;a.push_back(0);
    using namespace GenHelper;
    z1=s;
    z2=unsigned((~s)^0x233333333U);
    z3=unsigned(s^0x1234598766U);
    z4=(~s)+51;
    for(int i=1;i<=n;i++){
        int x=rand_()&32767;
        int y=rand_()&32767;
        a.push_back(l+(x*32768+y)%(r-l+1));
    }
    return a;
}
const int N=1e7+5;
int n,ll,rr,mod;
unsigned s;
vector<int> xl;
int sta[N],top;
int ls[N],rs[N],rt;
int fro[N],sum[N],beh[N];
int ans;
void dfs(int x){
    if(ls[x])dfs(ls[x]);
    if(rs[x])dfs(rs[x]);
    fro[x]=(fro[ls[x]]+fro[rs[x]]*sum[ls[x]]%mod*xl[x]%mod+xl[x]*sum[ls[x]]%mod)%mod;
    beh[x]=(beh[rs[x]]+beh[ls[x]]*sum[rs[x]]%mod*xl[x]%mod+xl[x]*sum[rs[x]]%mod)%mod;
    sum[x]=sum[ls[x]]*sum[rs[x]]%mod*xl[x]%mod;
    if(!ls[x]&&!rs[x])fro[x]=beh[x]=sum[x]=xl[x];
    ans=(ans+(beh[ls[x]]+1)*(fro[rs[x]]+1)%mod*xl[x]%mod*xl[x]%mod)%mod;
}
signed main(){
    freopen("tio.in","r",stdin);
    freopen("tio.out","w",stdout);
    // cout<<(sizeof(xds.sm)*4+sizeof(sta)*4>>20)<<endl;
    scanf("%lld%u%lld%lld%lld",&n,&s,&ll,&rr,&mod);
    xl=get(n,s,ll,rr);
    fo(i,1,n){
        while(top&&xl[sta[top]]<xl[i])ls[i]=sta[top--];
        if(top)rs[sta[top]]=i;
        sta[++top]=i;
    }rt=sta[1];
    sum[0]=1;dfs(rt);
    printf("%lld",ans);
    return 0;
}

T4 肯德基

这就是一个板子题

有一个式子:\(\mu^2(x)=\sum\limits_{d^2|x}\mu(d)\)

其实你把这个式子带进去就一连串全出来了,所以我主要证一下这个式子

平方因子指的是两个相等的质数相乘所得的平方因子

首先对于不含有平方因子的数,答案应该是\(1\),只有\(1^2\)可整除,所以等于\(1\),在没有平方因子的情况下得证

其次如果有平方因子,答案应该是\(0\),我们设可以形成平方因子的质数的个数为\(k\)

为什么\(k\)是这样定义的,因为对于\(d\)来说,如果含有了平方因子,就不会有贡献了

所以我们需要每一个可以形成平方因子的质数的个数只有\(2\),所以只要能形成平方因子就可以满足此条件

对于\(d\)不含质因子的数来说,就一个\(1\),可以写成\({k \choose 0}\)个,贡献是\(1\)

对于\(d\)只含有一个质因子的数,有\({k \choose 1}\)个,贡献是\(-1\)

对于\(d\)含有两个质因子的数,有\({k \choose 2}\)个,贡献是\(1\)

......

可以写成这样的形式:

\[\sum\limits_{i=0}^{k}{k \choose i}1^{k-i}(-1)^i \]

于是这个东西就是二项式定理\(=(1-1)^k=0\)

所以对于含平方因子的数就得证了

AC_code
#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
const int N=1e7+5;
int T,n,inv2;
int p[N],mu[N],cnt;
bool vis[N];
int f[N];
int sum(int x){
    if(x&1)return x*((x+1)/2);
    else return x/2*(x+1);
}
int sol(int x){
    int ret=0,l=1,r,sq=sqrt(x);
    for(;l<=sq;l=r+1){
        r=max(l,(int)sqrt(n/(n/l/l)));
        ret+=(f[r]-f[l-1])*sum(n/l/l);
    }
    return ret;
}
signed main(){
    freopen("kfc.in","r",stdin);
    freopen("kfc.out","w",stdout);
    // cout<<(sizeof(mu)*4>>20)<<endl;
    n=1e7;mu[1]=1;
    fo(i,2,n){
        if(!vis[i])p[++cnt]=i,mu[i]=-1;
        for(int j=1;j<=cnt&&i*p[j]<=n;j++){
            vis[i*p[j]]=true;
            if(i%p[j]==0){mu[i*p[j]]=0;break;}
            else mu[i*p[j]]=-mu[i];
        }
    }
    fo(i,1,n)f[i]=mu[i]*i*i;
    fo(i,1,n)f[i]+=f[i-1];
    scanf("%llu",&T);
    fo(i,1,T){
        scanf("%llu",&n);
        printf("%llu\n",sol(n));
    }
    return 0;
}
posted @ 2021-11-01 16:32  fengwu2005  阅读(48)  评论(1)    收藏  举报