20170814四校联考

啊啊啊啊啊啊NOIAu大神ysy出题虐菜出人命啦!爆tan(pi/4)啦!被害者家属情绪稳定。

ysy大佬谁敢D啊,NOIAu1st了,只适合D人了。

还是IOIAu的大佬体谅人,我还那么蒟蒻呢~

闲话不说,上题目:

T1:

宝石(gem)

【题目描述】
有 n 座城市,编号为 1~n,第 i 座城市里宝石的交易价格为 ai。当
你经过第 i 座城市时,你可以以 ai 的价格购买或卖出一个宝石。在任
意时刻,你最多只能携带一个宝石。
有 m 次操作,操作分为两种:
(1) 给定l,r,询问依次经过编号为l~r的城市能获得的最大收益。
(2) 给定 l,r,x,y,将 al 至 ar 修改为首项为 x,公差为 y 的等差数
列。
【输入数据】
第一行两个整数 n,m,第二行 n 个整数 a1~an,接下来 m 行每行
第一个整数表示操作编号,接下来一些整数表示操作参数。
【输出数据】
对于每个操作 1,输出一行一个整数表示答案。
【样例输入】
6 5
3 2 1 2 3 4
1 1 5
2 1 3 1 1
1 2 6
1 1 6
1 3 4
【样例输出】
2
3
4
0
【数据范围】
对于 20%的数据,n,m<=1000。
对于另外 30%的数据,不存在操作 2。
对于又另外 20%的数据,操作 2 中 l=r。
对于又另外 20%的数据,操作 2 中 y=0。
对于 100%的数据,n,m<=200000,1<=l<=r<=n,1<=ai,x<=10^9,
|y|<=10^4。

题解:

由于等差数列的缘故,很容易看出来需要用线段树维护差分序列。

在城市 i 购买一个宝石,在城市 j 卖出,等价于在城市i 购买,城市 i+1 卖出,城市 i+1 购买,城市 i+2 卖出……城市 j-1 购买,城市 j 卖出。那么问题变为只能在相邻两座城市购买和卖出。

记 b i =max(a i -a i-1 ,0),那么我们要求的就是

将 a 的一段修改为等差数列,等价于将 b 的一段修改为同一个值并且单独修改两个位置。分别用线段树维护 a和 b 即可。

时间复杂度 O(n+mlogn)

 

代码(线段树神他妈长):

#include<cstdio>
#include<cstring>
#define Fn "gem"
#define r register
typedef long long ll;
inline int read(){
    r int x=0,f=1;r char ch=getchar();
    for(;ch>'9'||ch<'0';f=ch=='-'?-1:1,ch=getchar());
    for(;ch<='9'&&ch>='0';x=(x<<3)+(x<<1)+ch-'0',ch=getchar());
    return x*f;
}
#define l 1LL
inline ll read1(){
    r ll x=0;r int f=1;r char ch=getchar();
    for(;ch>'9'||ch<'0';f=ch=='-'?-1:1,ch=getchar());
    for(;ch<='9'&&ch>='0';x=((x<<3)+(x<<1)+ch-'0')*l,ch=getchar());
    return x*f*l;
}
#undef l
#undef r
#define MN 2100000
int n,m,w[1000010],x[MN],y[MN],a[MN],b[MN],c[MN],p;
ll f[MN];
#define ls (i<<1)
#define rs (i<<1|1)
#define mid (j+k>>1)
#define len (k-j+1)
inline void pushdown(int i,int k){
    if(x[i]){
           x[ls]=x[rs]=1;
           y[ls]=y[rs]=y[i];
           f[ls]=f[rs]=(ll)y[i]*k>>1;
        x[i]=0;
    }
}
inline ll query(int i,int j,int k,int l,int r){
    if(l<=j&&k<=r)return f[i];
    ll p=0;
    pushdown(i,len);
    if(l<=mid)p+=query(ls,j,mid,l,r);
    if(r>mid)p+=query(rs,mid+1,k,l,r);
    return p;
}
inline void update(int i,int j,int k,int l,int r,int p){
    if(l<=j&&k<=r){
           x[i]=1;
           y[i]=p;
           f[i]=(ll)p*len;
    }
    else{
           pushdown(i,len);
           if(l<=mid)update(ls,j,mid,l,r,p);
           if(r>mid)update(rs,mid+1,k,l,r,p);
           f[i]=f[ls]+f[rs];
    }
}
inline void pushdown1(int i,int k){
    if(a[i]){
           a[ls]=a[rs]=1;
           b[ls]=b[i];b[rs]=b[i]+(c[i]*k>>1);
           c[ls]=c[rs]=c[i];
        a[i]=0;
    }
}
inline int query(int i,int j,int k,int p){
    if(j==k)return b[i];
    pushdown1(i,len);
    if(p<=mid)return query(ls,j,mid,p);
    return query(rs,mid+1,k,p);
}
inline void update(int i,int j,int k,int l,int r,int p,int q){
    if(l<=j&&k<=r){
           a[i]=1;
           b[i]=p;
           c[i]=q;
    }
    else{
           pushdown1(i,len);
           if(l<=mid){
              update(ls,j,mid,l,r,p,q);
              p+=q*(mid+1-l);
            l=mid+1;
        }
           if(r>mid)update(rs,mid+1,k,l,r,p,q);
    }
}
int main(){
#define max(a,b) (a>b?a:b)
#define r register
    freopen(Fn".in","r",stdin);
    freopen(Fn".out","w",stdout);
    r int p=1;
    n=read();m=read();
    for(;p<n;p<<=1);
    for(r int i=1;i<=n;i++)w[i]=read(),update(1,1,p,i,i,w[i],0);
    for(r int i=2;i<=n;i++)f[p+i-1]=max(w[i]-w[i-1],0);
    for(r int i=p-1;i>0;i--)f[i]=f[ls]+f[rs];
    while(m--){
           r int opt=read();
           if(opt==1){
              r int i=read(),j=read();
              printf("%I64d\n",i==j?0:query(1,1,p,i+1,j));
        }
           else{
              r int i=read(),j=read(),k=read(),l=read();
              update(1,1,p,i,j,k,l);
              if(i<j)update(1,1,p,i+1,j,max(l,0));
              if(i>1)update(1,1,p,i,i,max(k-query(1,1,p,i-1),0));
              if(j<n)update(1,1,p,j+1,j+1,max(query(1,1,p,j+1)-(k+(j-i)*l),0));
        }
    }
    return 0;
}
View Code

(代码很丑,宏用的太多了,大家不要学习啊)


T2:

01 串(string)
【题目描述】
给定正整数 m 以及 n 个 01 串 s1~sn,你需要求出长度为 2m 的反
对称的包含这 n 个01 串作为子串的01 串的个数。对998244353 取模。
一个 01 串 s 是反对称的当且仅当它对于 1<=i<=|s|都满足 s[i] ≠
s[|s|-i+1]。
【输入数据】
第一行两个整数 n,m。接下来 n 行每行一个字符串 s1~sn。
【输出数据】
一行一个整数表示答案。
【样例输入】
2 3
011
001
【样例输出】
4
【数据范围】
对于 10%的数据,m<=15。
对于 40%的数据,n<=4,|si|<=20。
对于 60%的数据,n<=6,|si|<=30,m<=100。
对于另外 20%的数据,n=1。
对于 100%的数据,n<=6,|si|<=100,m<=500。

题解:

(字符串处理哪家强,AC自动机加DP)

每个串有 4 种情况:在前一半出现,在后一半出现,跨越中间并且在前一半的长度大,跨越中间并且在后一半的长度大。

前两种情况只要在正串和反串(翻转+取反)的 AC 自动机上状压 dp 即可。

对于第三种情况,我们需要对正串的所有超过一半的前缀判断如果前一半以它结尾是否会构造出这个串,第四种情况就对反串这样处理一下。dp 结束的时候,将字符串的出现情况对结束位置对应的跨越中间的字符串取个并。

时间复杂度 O(2^n*n*|si|*m)

 

代码:

#include<cstdio>
#include<cstring>
#define Fn "string"
#define mod 998244353
#define r register
int n,m,x[1210][10],f[510][1210][70],g[1210],p,lim;
char s[110];
inline void swap(char &a,char &b){r char t=a;a=b;b=t;}
inline bool check(int j,int n){
    for(r int i=j+1;i<n;i++)
          if(j-(i-j)+1<0||s[j-(i-j)+1]==s[i])return 0;
    return 1;
}
inline void add(int k){
    r int n=strlen(s);
    for(r int i=0,j=0;j<n;j++){
           if(!x[i][s[j]-'0']){
              x[i][s[j]-'0']=++p;
              x[p][2]=i;
              x[p][5]=s[j]-'0';
        }
           i=x[i][s[j]-'0'];
           if(check(j,n))x[i][4]|=k;
        if(j==n-1)x[i][3]|=k;
    }
}
int main(){    
    freopen(Fn".in","r",stdin);
    freopen(Fn".out","w",stdout);
    scanf("%d%d",&n,&m);
    for(r int i=1;i<=n;i++){
        scanf("%s",&s);
        add(1<<i-1);
        r int l=strlen(s);
        for(r int j=0;l-1-j>j;j++)swap(s[j],s[l-1-j]);
        for(r int j=0;j<l;j++)s[j]^=1;
        add(1<<i-1);
    }
    g[lim=1]=0;
    for(r int u=1,i=g[u];u<=lim;u++,i=g[u]){
        if(x[i][0])g[++lim]=x[i][0];
        if(x[i][1])g[++lim]=x[i][1];
        if(!i)continue;
        r int j=x[x[i][2]][6];
        for(;j&&!x[j][x[i][5]];j=x[j][6]);
        if(x[j][x[i][5]]&&x[j][x[i][5]]!=i)x[i][6]=x[j][x[i][5]];
        if(!x[i][0])x[i][0]=x[x[i][6]][0];
        if(!x[i][1])x[i][1]=x[x[i][6]][1];
        for(j=3;j<=5;j++)x[i][j]|=x[x[i][6]][j];
    }
    f[0][0][0]=1;
    for(r int i=1;i<=m;i++)
        for(r int j=0;j<=p;j++)
            for(r int k=0;k<(1<<n);k++)
                 for(r int l=0;l<=1;l++){
                    r int u=x[j][l],v=k|x[u][3];
                    f[i][u][v]=(f[i][u][v]+f[i-1][j][k])%mod;
                   }
    for(r int i=lim=0;i<=p;i++)
        for(r int j=0;j<(1<<n);j++)
            if((j|x[i][4])==(1<<n)-1)
                 lim=(lim+f[m][i][j])%mod;
    printf("%d\n",lim);
    return 0;
}
View Code

 

T3:

矩阵(matrix)
【题目描述】
有一个 n 行 m 列的矩阵,你需要将每一格染上黑色或白色。
有 q 次询问,每次询问给出 ai,bi,你需要求出至少有 ai 行,至少
有 bi 列全是黑格的方案数。对 998244353 取模。
【输入数据】
第一行三个整数 n,m,q,接下来 q 行每行两个整数 ai,bi。
【输出数据】
q 行,每行一个整数表示答案。
【样例输入】
3 4 1
1 2
【样例输出】
169
【数据范围】

对于 100%的数据,0<=ai<=n,0<=bi<=m。

题解:

啊啊啊啊啊啊啊啊容斥原理!!!

抱歉公式打不出来只好上图了~

代码:

#include<cstdio>
#include<cstring>
#define mod 998244353
#define max(a,b) (a>b?a:b)
#define MN 4005
#define r register
#define Fn "matrix"
typedef long long ll;
inline int read(){
    r int x=0,f=1;r char c=getchar();
    for(;c<'0'||c>'9';f=c=='-'?-1:1,c=getchar());
    for(;c>='0'&&c<='9';x=(x<<3)+(x<<1)+c-'0',c=getchar());
    return x*f;
}
int n,m,ans,q;
int c[MN][MN],f[MN][MN],a[MN],b[MN],x[MN*MN];
int main(){
    freopen(Fn".in","r",stdin);
    freopen(Fn".out","w",stdout);
    n=read();m=read();q=read();
    r int mx=max(m,n),mn=m*n;
    for(r int i=0;i<=mx;i++){
        c[i][0]=1;
        for(r int j=1;j<=i;j++)
            c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
    }
    x[0]=1;
    for(r int i=1;i<=mn;i++)
        x[i]=(x[i-1]<<1)%mod;
    for(r int i=0;i<=n;i++)
        for(r int j=0;j<=m;j++)
            f[i][j]=(ll)c[n][i]*c[m][j]%mod*x[(n-i)*(m-j)]%mod;
    while(q--){
        r int y=read(),z=read();
        a[y]=1;
        for(r int i=y+1;i<=n;i++){
            a[i]=1;
            for(r int j=y;j<i;j++)
                a[i]=(a[i]-(ll)a[j]*c[i][j])%mod;
        }
        b[z]=1;
        for(r int i=z+1;i<=m;i++){
            b[i]=1;
            for(r int j=z;j<i;j++)
                b[i]=(b[i]-(ll)b[j]*c[i][j])%mod;
        }
        ans=0;
        for(r int i=y;i<=n;i++)
            for(r int j=z;j<=m;j++)
                ans=(ans+(ll)a[i]*f[i][j]%mod*b[j])%mod;
        printf("%d\n",(ans+mod)%mod);
    }
    return 0;
}
View Code

 

好了,乘这几天多打点题,一周以后再来被虐!

posted @ 2017-08-14 13:25  AristocratMarser  阅读(412)  评论(0编辑  收藏  举报