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

A.挤压

经典二进制拆位

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

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

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

UPD: 码的谁问的,显然这里不是枚举 \(\sum_{i\operatorname{and}j}2^{i}\times 2^{j}\) 再相加,你好歹得套个期望吧

\[E(s^2(x))=\sum_{i,j}E(x_i=1)\times E(x_j=1)\times 2^{i}\times 2^{j} \]

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

最后统计的时候直接从 \(i=n,l=1,m=1\) 的状态里加和即可,要注意乘以 \(2^i\times 2^j\),统计的时候要注意 \(f_{n,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\) \(S\infty n\)

C.星空遗迹

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

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

这样做的依据:

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

转化成式子大概就是

\[f_i=\begin{cases}f_{i-1}+1&\text{lose}\\f_{i-1}&\text{draw}\\\max(1,f_{i-1}-1)&\text{win}\end{cases} \]

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

感性理解

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

然后上线段树

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

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

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

#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 @ 2024-10-05 07:10  HaneDaniko  阅读(45)  评论(13编辑  收藏  举报