[40](CSP 集训)CSP 联训模拟 2

A.挤压

经典二进制拆位

好像也不是那么经典,CL-22 有提到

那么这个题因为维护的不是贡献和而是贡献平方和,所以考虑怎么计算

假设我们得到了一个异或后的答案 x,不动脑子的写成 x=(k121+k222)2,二项式定理可以拆开,变成 iandj2i×2j (当 i 有值时,其贡献为 2ij 同理,拆分后贡献为二者相乘,可以发现,这两者中一旦有一个为 0,则其贡献也为 0,乘起来也就是 0,所以不做统计,关于如何判断 i,j 有值,CL-22 里有说,就是先拆位开桶,然后如果是奇数就有值,偶数就没有),发现是只有两项的形式,所以直接枚举即可

UPD: 码的谁问的,显然这里不是枚举 iandj2i×2j 再相加,你好歹得套个期望吧

E(s2(x))=i,jE(xi=1)×E(xj=1)×2i×2j

fi,j,k,l,m 表示考虑到第 i 位数字,考虑其 j,k 两位,l,m{0,1} 表示其有值/无值(其实也就是出现的 1 的个数是奇数还是偶数)的概率,注意这里求的概率是关于 “从前 i 个里选出若干个,最后的结果的第 i,j 位实现上述状态的概率”,然后你从 i 转移到 i+1 的时候,直接去判断当前选还是不选,如果选的话,判断一下新加入的值的第 i,j 位是否有值,有的话就将对应奇偶性取反

最后统计的时候直接从 i=n,l=1,m=1 的状态里加和即可,要注意乘以 2i×2j,统计的时候要注意 fn,j,j,1,1 这样的结果也要算进去,因为平方拆开是有自平方项的

提前预处理逆元和真实概率,否则跑的贼慢

跟 int_R 学到了炫酷写法

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=1e9+7;
int n;
int a[100001],p0[100001];
int f[2][33][33][2][2];
int power(int a,int t){
    int base=a,ans=1;
    while(t){
        if(t&1){
            ans=ans*base%p;
        }
        base=base*base%p;
        t>>=1;
    }
    return ans;
}
const int inv=power(1000000000,p-2);
signed main(){
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%lld",&n);
    for(int i=1;i<=n;++i){
        scanf("%lld",&a[i]);
    }
    for(int i=1;i<=n;++i){
        scanf("%lld",&p0[i]);
        p0[i]=p0[i]*inv%p;
    }
    for(int i=0;i<=32;++i){
        for(int j=0;j<=32;++j){
            f[0][i][j][0][0]=1;
        }
    }
    for(int i=1;i<=n;++i){
        for(int j=0;j<=32;++j){
            for(int k=0;k<=32;++k){
                for(int l:{0,1}){
                    for(int m:{0,1}){
                        f[i&1][j][k][l][m]=(f[1-(i&1)][j][k][l][m]*(1-p0[i])+f[1-(i&1)][j][k][l^((a[i]>>j)&1)][m^((a[i]>>k)&1)]*p0[i])%p;
                    }
                }
            }
        }
    }
    int ans=0;
    for(int i=0;i<=32;++i){
        for(int j=0;j<=32;++j){
            ans+=(1ll<<(i+j))%p*f[n&1][i][j][1][1]%p;
            ans%=p;
        }
    }
    cout<<(ans%p+p)%p;
}

B.工地难题

Update Sn

C.星空遗迹

维护一个胜利者栈,在这个栈里保证前一个元素能赢后一个,只要维护出这个栈,则栈底元素即为胜利者

  • 栈空,插入
  • 栈顶元素输了或平局,弹出
  • 栈顶元素赢了,插入新元素

这样做的依据:

  • 两个可以胜中间元素的元素,将其中间元素清除后结果不变(弹掉失败者的依据)
  • 一个相同元素的连通块,缩成一个后不影响结果(弹掉平局者的依据)

转化成式子大概就是

fi={fi1+1losefi1drawmax(1,fi11)win

你无视这个对 1max,发现在整个过程中的 fi 的最小值即为要求的字符所在地

感性理解

所以如果你无视掉这个对 1max,那么我们就可以通过维护原数组的最小值来求解

然后上线段树

可以维护一个原数组的差分数组,这个差分数组只有 1,0,1 三种取值,然后我们对查分数组的前缀和维护一颗线段树,因为我们单点修改时,相当于对差分数组做出最多一项修改,在前缀和上体现就是将 [i,n] 的所有值整体平移,那么我们就需要一个支持区间修改,查询最小值所在位置的线段树,查询位置也很简单,你只需要在树节点里记录一下 pos 信息

然后你做修改的时候(比如把差分数组 dxi 变成 dxi),直接对 [i,n] 增加一个 dxidxi 就行了,非常好写

然后需要注意的就是,如果遇到两个都是最小值,那么应该取下标靠后的那个

#include<bits/stdc++.h>
using namespace std;
int n,q;
inline int ton(char c){
    if(c=='R') return 0;
    if(c=='P') return 2;
    return 1;
}
inline char toc(int i){
    if(i==0) return 'R';
    if(i==2) return 'P';
    return 'S';
}
inline bool win2(int x,int y){
    if(x==0 and y==1) return true;
    if(x==1 and y==2) return true;
    if(x==2 and y==0) return true;
    return false;
}
inline int win(int s,int x){
    return win2(s,x);
    // -1 front win
    // if(s==x) return 0;
    // if(s==1 and x==2) return 1;
    // if(s==1 and x==3) return -1;
    // if(s==2 and x==3) return 1;
    // if(s==2 and x==1) return -1;
    // if(s==3 and x==1) return 1;
    // if(s==3 and x==2) return -1;
}
const int inf=0x3f3f3f3f;
int sum[200001];
namespace stree{
    struct tree{
        int l,r;
        int val,pos;
        int lazy;
    }t[800001];
    #define tol (id*2)
    #define tor (id*2+1)
    #define mid(l,r) mid=((l)+(r))/2
    void pushup(int id){
        if(t[tol].val<=t[tor].val){
            t[id].val=t[tol].val;
            t[id].pos=t[tol].pos;
        }
        else{
            t[id].val=t[tor].val;
            t[id].pos=t[tor].pos;
        }
    }
    void build(int id,int l,int r){
        t[id].l=l;t[id].r=r;
        if(l==r){
            t[id].val=sum[l];
            t[id].pos=l;
            return;
        }
        int mid(l,r);
        build(tol,l,mid);
        build(tor,mid+1,r);
        pushup(id);
    }
    void pushdown(int id){
        if(t[id].lazy){
            t[tol].val+=t[id].lazy;
            t[tol].lazy+=t[id].lazy;
            t[tor].val+=t[id].lazy;
            t[tor].lazy+=t[id].lazy;
            t[id].lazy=0;
        }
    }
    void change(int id,int l,int r,int val){
        // cout<<"change "<<id<<" "<<l<<" "<<r<<" "<<val<<endl;
        if(l<=t[id].l and t[id].r<=r){
            t[id].val+=val;
            t[id].lazy+=val;
            return;
        }
        pushdown(id);
        int mid(t[id].l,t[id].r);
        if(mid>=l) change(tol,l,r,val);
        if(mid<r) change(tor,l,r,val);
        pushup(id);
    }
    tree ask(int id,int l,int r){
        if(l<=t[id].l and t[id].r<=r) return t[id];
        pushdown(id);
        int mid(t[id].l,t[id].r);
        tree res={0,0,inf,0,0};
        if(mid>=l) res=ask(tol,l,r);
        if(mid<r){
            if(res.val>=inf) res=ask(tor,l,r);
            else{
                tree res2=ask(tor,l,r);
                if(res2.val<res.val) res=res2;
            }
        }
        return res;
    }
}
int a[200001];
stack<int>st;
int solve(){
    // for(int i=1;i<=n;++i){
    //     cout<<a[i]<<" ";
    // }
    // cout<<endl;
    while(!st.empty()) st.pop();
    int lastans=0;
    for(int i=1;i<=n;++i){
        while(!st.empty() and win2(st.top(),a[i])==false) st.pop();
        st.push(a[i]);
        if(st.size()==1) lastans=st.top();
    }
    return lastans;
}
int d[200001];
int main(){
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d %d",&n,&q);getchar();
    for(int i=1;i<=n;++i){
        a[i]=ton(getchar());
    }
    d[1]=1;
    for(int i=2;i<=n;++i){
        if(win(a[i-1],a[i])) d[i]=1;
        else if(win(a[i],a[i-1])) d[i]=-1;
        else d[i]=0;
    }
    for(int i=1;i<=n;++i){
        // cout<<d[i]<<" ";
        sum[i]=sum[i-1]+d[i];
    }
    // cout<<endl;
    stree::build(1,1,n);
    while(q--){
        int op;scanf("%d",&op);
        int k,l,r;char x;
        if(op==1){
            scanf("%d %c",&k,&x);
            a[k]=ton(x);
            int bef,aft,now=ton(x);
            if(k!=1){
                bef=d[k];aft=0;
                if(win(a[k-1],now)) aft=1;
                if(win(now,a[k-1])) aft=-1;
                d[k]=aft;
                stree::change(1,k,n,aft-bef);
                // cout<<aft<<" "<<bef<<" 1"<<aft-bef<<endl;
            }
            if(k!=n){
                bef=d[k+1];aft=0;
                if(win(now,a[k+1])) aft=1;
                if(win(a[k+1],now)) aft=-1;
                d[k+1]=aft;
                stree::change(1,k+1,n,aft-bef);
                // cout<<aft<<" "<<bef<<" 2"<<aft-bef<<endl;
            }
            a[k]=now;
        }
        else{
            scanf("%d %d",&l,&r);
            printf("%c\n",toc(a[stree::ask(1,l,r).pos]));
        }
    }
}

posted @   HaneDaniko  阅读(49)  评论(13编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示