NOIP 模拟

时间:2019-09-28
本文章向大家介绍NOIP 模拟,主要包括NOIP 模拟使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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}} \right \rfloor+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\n%lld",x.first,x.second);
    return 0;
}
/*
2 3
1 2 3
*/
big

原文地址:https://www.cnblogs.com/sto324/p/11603990.html