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> #includeView Code<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; } #definel 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; }
(代碼很醜,宏用的太多了,大家不要學習啊)
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
好了,乘這幾天多打點題,一周以後再來被虐!
20170814四校聯考