• NOIP 模拟 927


    string

    给定一个由小写字母组成的字符串 s。有 m 次操作,每次操作给定 3 个参数 l,r,x。如果 x=1,将 s[l]~s[r]升序排序;如果 x=0,将 s[l]~s[r]降序排序。你需要求出最终序列。

    $n,m<=10^5$

    题解

    首先将字符转化为数字,会发现值域为[0,25],而且我们对一段区间排序只需要知道他每个数有多少个即可,

    所以考虑桶排+线段树,对于每个节点开一个桶,记录区间数字信息。

    提取区间就把桶的信息合并就好了,然后就是修改,因为值域很小,所以排序后对于一个数值就是区间覆盖,于是cover数组就出来了。

    考虑提取区间之后就要把原来的数给清掉,一开始是memset,但是其实我们已经用一个结构体存下了需要的信息,所以我们把这一部分减去就好,但是为了不在开结构体变量的时候清0,就需要将三种都写出来。

    考虑区间覆盖,基本上是普通操作,要讲的就是更新信息,因为每次都是改一个值,所以没必要都for一遍,但如果之前是memset就需要全部重新计算。值域覆盖区间的贡献就是val*(min(l,a_l)-max(r,a_r)+1),和标记永久化那个有点像,就是交集。

    然后你会发现你提取出区间后,他管辖的小区间并不知道,所以还需要在提取区间时搞个标记26(26也不会用到)。

    最后输出就遍历线段树就好了,所以我们还需要存这个位置是啥数字,不过只关心叶子结点就好。

    我本地跑贼慢,所以修改代码,卡了一年的常数,最后迫不得已(不要脸)地手动开O2,还是没卡进1s,不过上面居然过了,而且事实表明我去掉O2甚至更快(0.6s不到)。

    然后还有啥26个线段树,还有区间一样就直接输出什么的,知道就好。

    (代码有点乱,还是因为在乱改)

    #include<ctime>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    #define re register
    const int maxn=100005;
    int n,m,cnt;
    char s[maxn];
    int root,ls[maxn<<1],rs[maxn<<1],cover[maxn<<1];
    struct point{
        int num[27],a;
    }tr[maxn<<1];
    
    template<class T>inline void read(T &x){
        x=0;int f=0;char ch=getchar();
        while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
        while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
        x = f ? -x : x ;
    }
    
    int max(int x,int y){return x>y ? x : y ;}
    int min(int x,int y){return x<y ? x : y ;}
    
    void update(point &ret,point x,point y){
        for(re int i=0;i<26;i++) 
         ret.num[i]=x.num[i]+y.num[i];
    }
    
    void build(int &rt,int l,int r){
        rt=++cnt;
        cover[rt]=-1;
        if(l==r){
            //memset(tr[rt].num,0,sizeof(tr[rt].num));
            tr[rt].a=s[l]-'a';
            tr[rt].num[s[l]-'a']++;
            return ;
        }
        int mid=(l+r)>>1;
        build(ls[rt],l,mid);
        build(rs[rt],mid+1,r);
        update(tr[rt],tr[ls[rt]],tr[rs[rt]]);
    }
    
    void put_cover(int rt,int l,int r,int val){
        cover[rt]=val;
        tr[rt].a=val;
        for(re int i=0;i<26;i++) tr[rt].num[i]=0;
        tr[rt].num[val]=r-l+1;
    }
    
    void push_down(int rt,int l,int r){
        int mid=(l+r)>>1;
        put_cover(ls[rt],l,mid,cover[rt]);
        put_cover(rs[rt],mid+1,r,cover[rt]);
        cover[rt]=-1;
    }
    
    point query(int rt,int l,int r,int a_l,int a_r){
        if(a_l<=l&&r<=a_r) {point cx=tr[rt];put_cover(rt,l,r,26);return cx;}
        if(cover[rt]!=-1) push_down(rt,l,r);
        int mid=(l+r)>>1;
        point ret;
        if(a_r<=mid) ret=query(ls[rt],l,mid,a_l,a_r);
        else if(mid<a_l) ret=query(rs[rt],mid+1,r,a_l,a_r);
        else update(ret,query(ls[rt],l,mid,a_l,a_r),query(rs[rt],mid+1,r,a_l,a_r));
        for(re int i=0;i<26;i++) tr[rt].num[i]-=ret.num[i];
        return ret;
    }
    
    void modify(int rt,int l,int r,int a_l,int a_r,int val){
        if(a_l<=l&&r<=a_r){
            put_cover(rt,l,r,val);
            return ;
        }
        if(cover[rt]!=-1) push_down(rt,l,r);
        int mid=(l+r)>>1;
        if(a_l<=mid) modify(ls[rt],l,mid,a_l,a_r,val);
        if(mid<a_r) modify(rs[rt],mid+1,r,a_l,a_r,val);
        //update(tr[rt],tr[ls[rt]],tr[rs[rt]]);
        tr[rt].num[val]+=min(r,a_r)-max(l,a_l)+1;
    }
    
    void get(int rt,int l,int r){
        if(l==r){putchar(tr[rt].a+'a');return ;}
        if(cover[rt]!=-1) push_down(rt,l,r);
        int mid=(l+r)>>1;
        get(ls[rt],l,mid);
        get(rs[rt],mid+1,r);
    }
    
    int main(){
        freopen("string.in","r",stdin);
        freopen("string.out","w",stdout);
        read(n);read(m);
        scanf("%s",s+1);
        build(root,1,n);
        point cx;
        for(re int i=0;i<26;i++) cx.num[i]=0;
        for(re int i=1;i<=m;i++){
            int l,r,x;
            read(l);read(r);read(x);
            cx=query(root,1,n,l,r);
            if(x){
                for(re int j=0,k=l;j<26&&k<=r;j++)
                 if(cx.num[j]){
                      modify(root,1,n,k,k+cx.num[j]-1,j);
                      k+=cx.num[j];
                      cx.num[j]=0;
                 }
            }
            else {
                for(re int j=25,k=l;j>=0&&k<=r;j--)
                 if(cx.num[j]){
                      modify(root,1,n,k,k+cx.num[j]-1,j);
                      k+=cx.num[j];
                      cx.num[j]=0;
                 }
            }
        }
        get(root,1,n);
        return 0;
    }
    /*
    5 2
    cabcd
    1 3 1
    3 5 0
    */
    string

    matrix

    求出满足以下条件的 n*m 的 01 矩阵个数:
    (1) 第 i 行第 1~li 列恰好有 1 个 1。
    (2) 第 i 行第 ri~m 列恰好有 1 个 1。
    (3) 每列至多有 1 个 1。

    补充:(li,ri)不能放

    $n,m<=3000,答案对998244353取模$

    题解

    (DP真是乱来

    完全不知道状态咋来的

    定义f[i][j]为当前在第i有j个右区间已经填好了。(有一个题解说是因为每列只能填一个,所以拍成一行,我感觉这个解释好)

    lsum[],rsum[]为第i列时左区间端点和右区间端点的个数

    然后考虑转移:

    首先对于这一列,可以填也可以不填,填的话就是看有多少能填的位置rsum[i]-(j-1) (现在总共的右区间-已经选了的)

    我们只考虑了右区间,还有左区间,我们到i的时候有lsum[i]-lsum[i-1]个左区间必须给出位置,美剧处理的右区间个数,剩下的位置有i-j个,那么方案数就乘以这些需要给出位置的左区间的排列。

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    #define ll long long
    const int mod=998244353;
    const int maxn=3005;
    int n,m;
    int lsum[maxn],rsum[maxn];
    ll f[maxn][maxn];//当前处理到第i列,有j个右区间 
    
    template<class T>inline void read(T &x){
        x=0;int f=0;char ch=getchar();
        while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
        while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
        x = f ? -x : x ;
    }
    
    int main(){
        freopen("matrix.in","r",stdin);
        freopen("matrix.out","w",stdout);
        read(n);read(m);
        for(int i=1;i<=n;i++){
            int x,y;
            read(x);read(y);
            lsum[x]++;
            rsum[y]++;
        }
        f[0][0]=1;
        for(int i=1;i<=m;i++){
            f[i][0]=f[i-1][0];
            lsum[i]+=lsum[i-1];
            rsum[i]+=rsum[i-1];
            for(int j=1;j<=i;j++) f[i][j]=(f[i-1][j]+f[i-1][j-1]*(rsum[i]-(j-1)))%mod;
            //这一列不放,这一列要放有rsum[i]-(j-1)行可以放
            
            for(int k=0;k<=i;k++)
             for(int j=lsum[i-1];j<lsum[i];j++)
              f[i][k]=f[i][k]*(i-j-k)%mod;
            //还有sum[i]-sum[i-1]个左区间要填
            //剩下i-k个位置,方案数就是排列 
        }
        printf("%d",f[m][n]);
    }
    matrix

    big

    你需要在[0,2^n)中选一个整数 x,接着把 x 依次异或 m 个整数a1~am。在你选出 x 后,你的对手需要选择恰好一个时刻(刚选完数时、异或一些数后或是最后),将 x 变为"$(left lfloor frac{2x}{2^{n}} ight floor+2x)mod 2^{n}$ 。

    你想使 x 最后尽量大,而你的对手会使 x 最后尽量小。
    你需要求出 x 最后的最大值,以及得到最大值的初值数量。

    $n<=30,m<=10^{5},0<=a_{i}<2^{n}$

    题解

    最暴力的方法就是枚举x,找出变换后的最小值,求最小值的最大值。(没有人一个一个for????可以使用前缀异或和)

    考虑他给出的式子前一部分就是找出最高位,后半部分在mod了之后就是将剩下的部分左移一位。所以就相当于转了个圈,比如1234就会变成2341(当然这是假的二进制只是为了说明)

    根据位运算的特点,我们可以一开始就这样操作,在i之前异或的数也这样操作,在i之后就异或原数,这样就相当于是在i之后变换。

    当然我们也不能一个一个搞,考虑记录后缀异或和suf、变换序列的前缀异或cx,那么对于在i位置变换就是x需要异或的就是cx[i]^suf[i+1],然后考虑把m+1个这样的数丢进trie树。

    考虑在trie树上寻找答案:

    如果当前节点两个儿子都有,那么无论这一位选啥对手都可以把它变成0。

    如果只有0的儿子,那么你选1的话就会得到1。

    同理只有1的儿子,选0就会得到1.

    函数返回pair<int,int>当前最大值和最大值个数。

    遍历一遍就好了。

    注意最大值是答案而不是选的数。

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int maxn=100005;
    int n,m;
    ll mod,tmp;
    ll a[maxn],cx[maxn],suf[maxn];//cx:变换后的前缀异或和 suf:后缀异或和 
    int cnt=1,go[maxn*40][2];
    
    void add(int x){
        int now=1;
        for(int i=n-1;i>=0;i--){
            bool c=x&(1<<i);
            if(!go[now][c]) go[now][c]=++cnt;
            now=go[now][c];
        }
    }
    
    pair<int,int> dfs(int now,int s){
        if(s==-1) return make_pair(0,1);
        if(go[now][1]&&go[now][0]){//有两条路,对手能让你这一步得到0,选取能得到的最大的 
            pair<int,int> x,y;
            x=dfs(go[now][0],s-1);
            y=dfs(go[now][1],s-1);
            if(x.first==y.first) return make_pair(x.first,x.second+y.second);
            if(x.first>y.first) return x;
            return y;
        }
        else if(!go[now][0]&&go[now][1]){//只有1,这一位填0得到1 
            pair<int,int> x=dfs(go[now][1],s-1);
            x.first+=1<<s;
            return x;
        }
        else if(!go[now][1]&&go[now][0]){//只有0,这一位填1得到1 
            pair<int,int> x=dfs(go[now][0],s-1);
            x.first+=1<<s;
            return x;
        }
    }
    
    int main(){
        freopen("big.in","r",stdin);
        freopen("big.out","w",stdout);
        
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++) scanf("%d",&a[i]);
        for(int i=m;i;i--) suf[i]=suf[i+1]^a[i];
        for(int i=1;i<=m;i++){
            cx[i]=(2*a[i]/(1<<n)+2*a[i])%(1<<n);
            cx[i]^=cx[i-1];
        }
        for(int i=0;i<=m;i++)
            add(cx[i]^suf[i+1]);//用x异或后得到在i之后变换的答案
        pair<int,int> x=dfs(1,n-1);
        printf("%lld
    %lld",x.first,x.second);
        return 0;
    }
    /*
    2 3
    1 2 3
    */
    big
  • 相关阅读:
    (转载)显示调用构造函数和析构函数
    (转载)c库不正确问题
    (转载)LINUX UNBUNTU10.04 下 搭建OC编译环境
    (转载)Chrome 快捷键 整理版
    (转载)php循环检测目录是否存在并创建(循环创建目录)
    (转载)PHP 判断常量,变量和函数是否存在
    Dockerfile 指令
    JavaEE&Docker 容器示例
    docker 中运行 redis 服务
    docker 中运行 sshd 服务
  • 原文地址:https://www.cnblogs.com/sto324/p/11603990.html
Copyright © 2020-2023  润新知