[考试反思]0612四校联考第三轮day1:幻想
直播爆炸场。
上来干$T1$。看起来不难,就直接搞个容斥,也就简单的过了样例。
然后看$T2,3$,都只会暴力,尽量去卡常,然后得分都比预料之中的高一些。
但是我不知道我怎么想的,离考试结束前给$T1$挂了个对拍,出锅了,匆忙交暴力。
考后证明只是有一个变量名写挂了,丢了$60pts$。
细节始终在挂啊。。。难以避免虽然是真的,但下次对拍还是应该早点打(在还有下次的时候)
实力不济,就没什么好说的。
T1:进攻!
大意:给定一个$01$矩阵。求连续选择$K$次矩形,没有一次触及到$0$且这$K$个矩形有公共部分的方案数。$n,m \le 2 \times 10^3$
好像不是很好统计,最后想到的唯一的比较合理的方法还是去统计最后的交集是哪个矩形。
然后如果交集是一个$1\times 2$的矩形,它在$1 \times 1$时也会重复统计,所以考虑容斥。
不难发现一个矩形的容斥系数只与长宽有关而与具体位置无关,所以我们可以枚举所有长宽的子矩形进行容斥。
枚举所有矩形(四个顶点)判定是否可行。
然后做一个四维前缀和统计出覆盖每个矩形的矩形有多少个。带上容斥系数然后搞个快速幂。
然而如果有快速幂会变成$O(n^4log\ K)$可能过不去$100$的点,所以考虑怎么优化掉这一部分
(虽然没必要,大神$skyh$实测能过,可是我太菜了,听听蒟蒻的思路吧万一哪天卡常呢,蒟蒻常数太大了这么写本机跑了$5s$)
方案数最多的情况应该就是全$1$矩阵了,我们把四维前缀和都做出来然后发现最大的方案数不过是$5 \times 10^6$的样子。
所以一个直接的想法是预处理所有快速幂值,是$5 \times 10^6 \times log$。还是很慢。
然后可能会想到线筛处理快速幂,我没写但是应该也不会快。
仔细想一想,其实这个方案数的数字种数应该不会特别多。所以快速幂的时候记忆化一下就行了。复杂度$O(n^4)$。本机$0.6s$
(对,就这,可能只有蒟蒻觉得这东西很难想到所以有必要想一想吧)
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int mod=998244353; 4 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;} 5 int p[102][102][102][102],a[102][102],n,m,k,pw[6700005]; long long rate[102][102],ans; 6 char s[101]; 7 int main(){ 8 freopen("attack.in","r",stdin); 9 freopen("attack.out","w",stdout); 10 scanf("%d%d%d",&n,&m,&k); 11 for(int i=1;i<=n;++i){ 12 scanf("%s",s+1); 13 for(int j=1;j<=m;++j)a[i][j]=(s[j]-49)+a[i][j-1]; 14 }for(int j=1;j<=m;++j)for(int i=1;i<=n;++i)a[i][j]+=a[i-1][j]; 15 rate[1][1]=1;rate[1][2]=mod-1;rate[2][1]=mod-1; rate[2][2]=1; 16 for(int u=1;u<=n;++u)for(int d=n;d>=u;--d)for(int l=1;l<=m;++l)for(int r=m;r>=l;--r) 17 p[u][d][l][r]=(a[d][r]+a[u-1][l-1]-a[d][l-1]-a[u-1][r]?0:1) 18 +p[u-1][d][l][r]+p[u][d+1][l][r]+p[u][d][l-1][r]+p[u][d][l][r+1] 19 -p[u-1][d+1][l][r]-p[u-1][d][l-1][r]-p[u-1][d][l][r+1]-p[u][d+1][l-1][r]-p[u][d+1][l][r+1]-p[u][d][l-1][r+1] 20 +p[u-1][d+1][l-1][r]+p[u-1][d+1][l][r+1]+p[u-1][d][l-1][r+1]+p[u][d+1][l-1][r+1] 21 -p[u-1][d+1][l-1][r+1]; 22 for(int u=1;u<=n;++u)for(int d=n;d>=u;--d)for(int l=1;l<=m;++l)for(int r=m;r>=l;--r)if(p[u][d][l][r]){ 23 if(!pw[p[u][d][l][r]])pw[p[u][d][l][r]]=qp(p[u][d][l][r],k); 24 ans+=rate[d-u+1][r-l+1]*pw[p[u][d][l][r]]; 25 if(ans>=8226880248558387199)ans%=mod; 26 }printf("%lld",ans%mod); 27 }
接下来来考虑正解。
首先一个歪门斜道是:刚才已经把容斥系数表打出来了,你输出一下就会发现,其实除了$c_{1,1}=c_{2,2}=1,c_{1,2}=c_{2,1}=-1$。剩下的系数都是$0$。
所以好像就好做很多了。但是我们还是正经的用正常思路来理解一下:
如果联通块是一棵树,那么好像可以直接用点数减边数。也就是$(1,1)-(1,2)-(2,1)$
但是在矩阵上可能会有$(2,2)$这样的块。大胆猜测$(2,2)$的系数是$1$
我们发现有$nm-(n-1)m-n(m-1)+(n-1)(m-1)=1$也就是对于任意一个矩形,上述系数都是对的。
其余形状同理。所以就是没问题了。现在的问题是怎么快速得到包含一个小矩形的矩形有多少个。
四维前缀和歇了,我们考虑二维前缀和。容斥没歇,于是我们继续容斥。
我们可以对于每个点求出:以这个点为左上/左下/右上/右下角的矩形有多少个,弄个单调栈维护面积就可以了。
把这个东西做一下从左上角开始的二维前缀和。
然后用左上角在$(x,y)$左上方的减去左下角在$(x-1,y)$左上方的减去右上角在$(x,y-1)$左上方的再加回来被重复减掉的右下角在$(x-1,y-1)$左上方的。
这样我们就得到了包含$(x,y)$的矩形个数。其余同理。快速幂不需要什么优化。总复杂度$O(n^2logK)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 const int $=2002,mod=998244353; 5 int n,m,k,L[$][$],R[$][$],A[$][$],sz1,s1[$],p1[$],sz2,s2[$],p2[$],t1,t2; char s[$]; 6 ll ld[$][$],lu[$][$],rd[$][$],ru[$][$],ans; 7 int qp(ll b,int t=k,int a=1){for(b=(b%mod+mod)%mod;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;} 8 int main(){ 9 freopen("attack.in","r",stdin); 10 freopen("attack.out","w",stdout); 11 scanf("%d%d%d",&n,&m,&k); 12 for(int i=1;i<=n;++i){ 13 scanf("%s",s+1); 14 for(int j=1;j<=m;++j)A[i][j]=s[j]-48,L[i][j]=A[i][j]?L[i][j-1]+1:0; 15 for(int j=m;j;--j)R[i][j]=A[i][j]?R[i][j+1]+1:0; 16 }s1[0]=s2[0]=-1; 17 for(int j=1;t1=t2=sz1=sz2=0,j<=m;++j)for(int i=1;i<=n;++i){ 18 while(s1[t1]>=L[i][j])sz1-=s1[t1]*(p1[t1]-p1[t1-1]),t1--; 19 while(s2[t2]>=R[i][j])sz2-=s2[t2]*(p2[t2]-p2[t2-1]),t2--; 20 s1[++t1]=L[i][j]; p1[t1]=i; lu[i][j]=sz1+=s1[t1]*(p1[t1]-p1[t1-1]); 21 s2[++t2]=R[i][j]; p2[t2]=i; ru[i][j]=sz2+=s2[t2]*(p2[t2]-p2[t2-1]); 22 }p1[0]=p2[0]=n+1; 23 for(int j=1;t1=t2=sz1=sz2=0,j<=m;++j)for(int i=n;i;--i){ 24 while(s1[t1]>=L[i][j])sz1-=s1[t1]*(p1[t1-1]-p1[t1]),t1--; 25 while(s2[t2]>=R[i][j])sz2-=s2[t2]*(p2[t2-1]-p2[t2]),t2--; 26 s1[++t1]=L[i][j]; p1[t1]=i; ld[i][j]=sz1+=s1[t1]*(p1[t1-1]-p1[t1]); 27 s2[++t2]=R[i][j]; p2[t2]=i; rd[i][j]=sz2+=s2[t2]*(p2[t2-1]-p2[t2]); 28 } 29 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)rd[i][j]+=rd[i][j-1],ld[i][j]+=ld[i][j-1],ru[i][j]+=ru[i][j-1],lu[i][j]+=lu[i][j-1]; 30 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)rd[i][j]+=rd[i-1][j],ld[i][j]+=ld[i-1][j],ru[i][j]+=ru[i-1][j],lu[i][j]+=lu[i-1][j]; 31 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j) 32 (ans+=qp(rd[i][j]-ru[i-1][j]-ld[i][j-1]+lu[i-1][j-1]) 33 -qp(rd[i][j]-ru[i][j]-ld[i][j-1]+lu[i][j-1])-qp(rd[i][j]-ru[i-1][j]-ld[i][j]+lu[i-1][j]) 34 +qp(rd[i][j]-ru[i][j]-ld[i][j]+lu[i][j]))%=mod; 35 printf("%lld",(ans+mod)%mod); 36 }
T2:字符串
大意:有$n$个字符串$t_i$,每个字符串有权值$v_i$,对于每种长度有识别值$g_l$。每次询问会给出一个区间,询问区间内的串拿出来建$trie$。
对于每个节点$x$,若$A \times f(x) + B \times len(x) \ge C$则将$g_{len(x)}$加入集合中。其中$f(x)$表示子树所有节点的权值和。
设所有$t_i$的最大长度是$L$。那么$g_{1..L}$构成一个排列。问最后在$[1,L]$中随机一个区间,区间与集合内元素有交的方案数。
$n \le 10^5,\sum |t_i| \le 3 \times 10^5,Q \le 10^5$
有交转化为无交,那么答案就是$\frac{\sum (p_{i+1}- p_i)^2}{L^2}$。只与前驱和后继有瓜。
若干次无厘头的区间询问,也只能往莫队上想了。把所有字符串首尾相接进行分块,每一个字符都表示对$trie$的一个节点的$f$值进行修改。
可以简单的支持插入和删除单点。至于集合拿个$set$维护就好了。时间复杂度$O(n\sqrt{n} log \ n)$。
然而实际上可以不用$set$。写回滚莫队就不需要支持删除了,改成链表就行了。时间复杂度$O(n^{1.5})$。但我太菜并没有写。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int $=3e5+5,_=4500; 4 #define ll long long 5 int n,C,c[26][$],v[$],pc=1,R=1,L,g[$],m,P[$],w[$],len[$],Lp[$],Rp[$],cnt,al[$],b[$],z[$]; 6 ll Ans,ans[$],L2,F[$],A,B; char s[$]; 7 #define md (L+R>>1) 8 set<int>S; 9 void ins(int&p,int al,int v){ 10 if(!p)p=++pc; if(al)w[++cnt]=v,P[cnt]=p,len[p]=al,F[p]=al*A; if(!s[al])return; 11 ins(c[s[al]-97][p],al+1,v); 12 } 13 ll gcd(ll a,ll b){return b?gcd(b,a%b):a;} 14 struct Q{int l,r,id;}q[$]; 15 void print(ll x){ll g=gcd(x,L2);printf("%lld/%lld\n",(L2-x)/g,L2/g);} 16 void push(int x){ 17 if(!b[x]){ 18 auto it=S.lower_bound(x);int z=*it;int y=*(--it); S.insert(x); 19 Ans-=(z-y)*(z-y-1ll)-(z-x)*(z-x-1ll)-(x-y)*(x-y-1ll); 20 }b[x]++; 21 } 22 void pop(int x){ 23 if(b[x]==1){ 24 S.erase(x); auto it=S.lower_bound(x);int z=*it;int y=*(--it); 25 Ans+=(z-y)*(z-y-1ll)-(z-x)*(z-x-1ll)-(x-y)*(x-y-1ll); 26 }b[x]--; 27 } 28 void add(int x){F[P[x]]+=w[x]*B;z[P[x]]++;if(z[P[x]]&&F[P[x]]>=C&&!al[P[x]])push(g[len[P[x]]]),al[P[x]]=1;} 29 void dec(int x){F[P[x]]-=w[x]*B;z[P[x]]--;if((!z[P[x]]||F[P[x]]<C)&&al[P[x]])pop(g[len[P[x]]]),al[P[x]]=0;} 30 int main(){ 31 freopen("string.in","r",stdin); 32 freopen("string.out","w",stdout); 33 scanf("%d%lld%lld%d",&n,&A,&B,&C); 34 for(int i=1;i<=n;++i)scanf("%d",&v[i]); 35 for(int i=1;i<=n;++i)scanf("%s",s),Lp[i]=cnt+1,ins(R,0,v[i]),L=max(L,(int)strlen(s)),Rp[i]=cnt; 36 for(int i=1;i<=L;++i)scanf("%d",&g[i]); 37 scanf("%d",&m); 38 for(int i=1,l,r;i<=m;++i)scanf("%d%d",&l,&r),q[i]=(Q){Lp[l],Rp[r],i}; 39 sort(q+1,q+1+m,[](Q a,Q b){return a.l/_!=b.l/_?a.l/_<b.l/_:(a.l/_&1)^(a.r<b.r);}); 40 int l=1,r=0; S.insert(0); S.insert(L+1); Ans=L2=L*(L+1ll); 41 for(int i=1;i<=m;++i){ 42 while(r<q[i].r)add(++r); while(l>q[i].l)add(--l); 43 while(l<q[i].l)dec(l++); while(r>q[i].r)dec(r--); 44 ans[q[i].id]=Ans; 45 }for(int i=1;i<=m;++i)print(ans[i]); 46 }
T3:序列
大意:给定一个长为$n$的序列和$m$条形如“$a_k$必须是区间$[l,r]$中的最大/小值”的限制。最小化每个数字变化量的绝对值之和使得所有限制被满足。
$n\le 5 \times 10^3,m \le 1.5 \times 10^4,a_i \le 10^6$
保序回归问题。据说还是模板题。反正是论文题。
首先考虑如果$a_i = 0/1$怎么做。网络流最小割模型,一侧表示$1$一侧表示$0$,然后较小的向较大的连边(不同的建图可能会反过来)
可以发现这就足矣满足限制,因为是区间连边,所以再搞一个线段树优化建边就行了。
而保序回归问题的结论是:可以二分,设立$mid$,小于等于的看作$0$大于的看作$1$,然后一样的做。
根据被分到了哪边递归下去做就完事了。正确性见论文。我又不是队爷。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int $=25555,$$=2e6,inf=1e9; 4 int a[$],fir[$],la[$$],to[$$],w[$$],ec,lc[$],rc[$],pc,N,r1,r2,ans,o[$],n,m; 5 void link(int a,int b,int v){la[++ec]=fir[a];fir[a]=ec;to[ec]=b;w[ec]=v;} 6 void con(int a,int b,int v=inf){link(a,b,v);link(b,a,0);} 7 #define md (L+R>>1) 8 void build(int p1,int p2,int L=0,int R=N-1){ 9 if(L==R)return con(L+5,p1),con(p2,L+5); 10 con(lc[p1]=++pc,p1);con(rc[p1]=++pc,p1); 11 con(p2,lc[p2]=++pc);con(p2,rc[p2]=++pc); 12 build(lc[p1],lc[p2],L,md);build(rc[p1],rc[p2],md+1,R); 13 } 14 void ask(int l,int r,int p,int o,int p1=1,int p2=2,int L=0,int R=N-1){ 15 if(l>r)return; if(l<=L&&R<=r)return o?con(p1,p):con(p,p2); 16 if(l<=md)ask(l,r,p,o,lc[p1],lc[p2],L,md); 17 if(r>md)ask(l,r,p,o,rc[p1],rc[p2],md+1,R); 18 } 19 int d[$],q[$]; 20 int dfs(int p,int f){int r=f; 21 if(p==4)return f; 22 for(int i=fir[p],y;f&&(y=to[i]);i=la[i])if(d[y]==d[p]+1&&w[i]){ 23 int x=dfs(y,min(w[i],f)); 24 if(!x)d[y]=0; 25 w[i]-=x;w[i^1]+=x;f-=x; 26 }return r-f; 27 } 28 bool bfs(){ 29 for(int i=1;i<=pc;++i)d[i]=0; d[3]=1; q[1]=3; 30 for(int h=1,p,t=1;p=q[h],h<=t;++h) 31 for(int i=fir[p],y;y=to[i];i=la[i])if(!d[y]&&w[i])d[q[++t]=y]=d[p]+1; 32 return d[4]; 33 } 34 vector<int>l[$],r[$],O[$],v; 35 int lower(vector<int>&V,int x){return lower_bound(V.begin(),V.end(),x)-V.begin();} 36 int upper(vector<int>&V,int x){return upper_bound(V.begin(),V.end(),x)-V.begin()-1;} 37 void solve(int L,int R,vector<int>&V){ 38 if(L==R){for(int x:V)ans+=abs(L-a[x]);return;} 39 if(!V.size())return; 40 if(V.size()==1){ans+=max(max(a[V[0]]-R,L-a[V[0]]),0);return;} 41 N=V.size(); ec=1; pc=4; 42 for(int x:V){o[x]=++pc;if(a[x]>md)con(3,pc,1);else con(pc,4,1);} 43 build(r1=1,r2=2); 44 for(int x:V)for(int i=0;i<l[x].size();++i)ask(lower(V,l[x][i]),upper(V,r[x][i]),o[x],O[x][i]); 45 while(bfs())dfs(3,inf); 46 for(int i=1;i<=pc;++i)fir[i]=0; 47 vector<int>v0,v1; 48 for(int x:V)if(d[o[x]])v1.push_back(x);else v0.push_back(x); 49 V.clear();solve(L,md,v0);solve(md+1,R,v1); 50 } 51 int main(){ 52 freopen("sequence.in","r",stdin); 53 freopen("sequence.out","w",stdout); 54 scanf("%d%d",&n,&m); 55 for(int i=1;i<=n;++i)scanf("%d",&a[i]),v.push_back(i); 56 for(int i=1,o,L,R,k;i<=m;++i)scanf("%d%d%d%d",&o,&L,&R,&k),l[k].push_back(L),r[k].push_back(R),O[k].push_back(o); 57 solve(1,100000,v);printf("%d",ans); 58 }