GJOI 9.27/10.3 题解
1.CF1984D “a” String Problem
题意
给定一个由小写拉丁字母组成的字符串 sss。请计算有多少个非空字符串 t≠"a"t \neq \texttt{"a"}t="a",使得可以将 sss 分割成若干子串,满足以下条件:
- 每个子串要么等于 ttt,要么等于 "a"\texttt{"a"}"a";
- 至少有一个子串等于 ttt。
一个字符串 sss 的分割是一个有序的 kkk 个字符串 t1,t2,…,tkt_1, t_2, \ldots, t_kt1,t2,…,tk(称为子串)的序列,满足 t1+t2+…+tk=st_1 + t_2 + \ldots + t_k = st1+t2+…+tk=s,其中 +++ 表示连接操作。
多组测试数据,1≤t≤1041\le t\le 10^41≤t≤104,2≤∣s∣≤2×1052\le|s|\le 2\times 10^52≤∣s∣≤2×105,∑∣s∣≤3×105\sum |s|\le 3\times 10^5∑∣s∣≤3×105。
思路
由 ttt 子串都相同,先发现几个结论:
- ttt 中,每种非
a
字符个数、种类、出现顺序相同; - ttt 中,每个非
a
字符左侧、右侧的a
数量,不同位置的 ttt 子串对应相同,也即每两个非a
字符之间a
的个数对应相同。
于是我们记录 nanana 个非 a
字符的下标 naps1∼nanaps_{1\sim na}naps1∼na,ttt 中出现的非 a
字符记为 lenlenlen 个,显然 len∣nalen|nalen∣na,于是 O(d(n))O(d(n))O(d(n))(约数个数,约为 O(n)O(\sqrt{n})O(n))枚举长度。
子串的非 a
字符一定包含第 1∼len1\sim len1∼len 个非 a
字符;又因为每个子串 ttt 相同,根据上面的结论,需要满足原串上 naps1∼napslennaps_1\sim naps_{len}naps1∼napslen、napslen+1∼naps2lennaps_{len+1}\sim naps_{2len}napslen+1∼naps2len、……、napsna−len+1∼napsnanaps_{na-len+1}\sim naps_{na}napsna−len+1∼napsna 相同,即一个合格子串 ttt 去掉前缀、后缀的 a
为 t′t't′,t′t't′ 满足对应相同。
可以使用哈希判断。
ll ret=0,H=gethash(naps[1],naps[len]);
bool flag=1;
for(int j=len+1;j<=na;j+=len)//0~na-1 <- 0~i-1
{if(gethash(naps[j],naps[j+len-1])!=H){flag=0;break;}
}
能有多种子串 ttt 的原因,是因为可以改变前缀、后缀 a
的个数。考虑在去掉前缀、后缀的合法串前后分别加 head,tailhead,tailhead,tail 个 a
,head∈[0,naps1)head\in[0,naps_1)head∈[0,naps1),tail∈[0,∣s∣−napsna]tail\in[0,|s|-naps_{na}]tail∈[0,∣s∣−napsna]。
我们知道两个子串 ti−1,tit_{i-1},t_iti−1,ti 之间会隔着若干个 a
,这些 a
由 ti−1t_{i-1}ti−1 的后缀 tailtailtail 个 a
、原串上的 a
、tit_iti 前缀 headheadhead 个 a
组成。
因为子串不能重叠,所以去掉前缀后缀的 a
,ti−1′,ti′t'_{i-1},t'_iti−1′,ti′ 之间隔着 mim_imi 个 a
,head+tail≤mihead+tail\le m_ihead+tail≤mi。这个式子的充分条件是 head+tail≤min{mi}=mihead+tail\le \min \{m_i\}=mihead+tail≤min{mi}=mi。考虑枚举 head∈[0,naps1)head\in[0,naps_1)head∈[0,naps1),tailtailtail 的范围就是 [0,min(mi−head,∣s∣−napsna)][0,\min(mi-head,|s|-naps_{na})][0,min(mi−head,∣s∣−napsna)],取值种数并入贡献即可。
ll mi=inf,tail_a=n-naps[na];
for(int j=len+1;j<=na;j+=len)
{
// cout<<naps[j]<<" "<<naps[j-1]<<endl;mi=min(mi,naps[j]-naps[j-1]-1);
}
for(int head=0;head<naps[1];head++)//0~naps[1]的a
{ll tail=mi-head;if(tail<0)continue;ret+=min(tail_a,tail)+1;
}
代码
#pragma GCC optimise(2)
#pragma GCC optimise(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2e5+9,inf=3e14;
const ll base=19260817,mod=1e9+7;
ll Q,n;
char s[N];
ll na,ans,naps[N];
ll ha[N],pw[N];
void init()
{pw[0]=1;for(int i=1;i<N;i++)pw[i]=pw[i-1]*base%mod;
}
ll gethash(ll l,ll r)
{return (ha[r]-ha[l-1]*pw[r-l+1]%mod+mod)%mod;
}
ll sol(ll len)
{ll ret=0,H=gethash(naps[1],naps[len]);bool flag=1;for(int j=len+1;j<=na;j+=len)//0~na-1 <- 0~i-1{if(gethash(naps[j],naps[j+len-1])!=H){flag=0;break;}}if(!flag)return 0;
/* cout<<"循环节:";for(int j=1;j<=len;j++)cout<<naps[j]<<" ";cout<<endl;*/ll mi=inf,tail_a=n-naps[na];for(int j=len+1;j<=na;j+=len){// cout<<naps[j]<<" "<<naps[j-1]<<endl;mi=min(mi,naps[j]-naps[j-1]-1);}for(int head=0;head<naps[1];head++)//0~naps[1]的a {ll tail=mi-head;if(tail<0)continue;ret+=min(tail_a,tail)+1;}return ret;
}
int main()
{freopen("aa.in","r",stdin);freopen("aa.out","w",stdout);init();scanf("%lld",&Q);while(Q--){scanf("%s",s+1);n=strlen(s+1);na=0,ans=0;for(int i=1;i<=n;i++){ha[i]=(ha[i-1]*base%mod+(s[i]-'a'+1))%mod;if(s[i]!='a')naps[++na]=i;}if(na==0){printf("%lld\n",n-1);continue;}for(ll i=1;i*i<=na;i++){if(na%i)continue;ans+=sol(i);if(i*i!=na)ans+=sol(na/i);}printf("%lld\n",ans);}return 0;
}
2.P11352 NOISG2024 Finals Coin
3.Baekjoon 18760 Heavy Stones
4.CF1975D Paint the Tree
我的博客。
接下来是 GJOI 10.3,怎么国庆回来 333 天连打 333 场啊!而且题贼难补。
1.CF1942C2 Bessie’s Birthday Cake (Hard Version)
题意
题目传送门,建议前往原题阅读题面和样例。
思路
理想地,kkk 个点全部被选,被选的 m+km+km+k 个点组成一个 m+km+km+k 边形,可以分成 m+k−2m+k-2m+k−2 部分。于是考虑这 kkk 个点的分布。
为什么我们直接考虑在 m+k−2m+k-2m+k−2 边形上分呢?来看一张图:
同样把 BCDFBCDFBCDF 分成 222 部分,左图是固定 FFF 向 B,C,DB,C,DB,C,D 连边,用了 333 个点;右图是在 BDBDBD 劈开,只用了 222 个点——显然右图的割法是更优的。即每个两段放一个点
具体地,我们算出原来 mmm 个点之间间隔段数 cic_ici。我们发现,cic_ici 为偶数的时候,如上图 BBB 和 LLL 可以再形成一个三角形,因为隔了两条边;但若 cic_ici 为奇数,这最后一段贡献没法多出来。
又因为 kkk 个候选点的贡献相互独立,于是我们优先填满 cic_ici 为偶数的间隔。
ll ans=m+k-2;
sort(a+1,a+m+1);
c[m]=a[1]+n-a[m];
for(int i=1;i<m;i++)
c[i]=a[i+1]-a[i];
ll tem=k;
no=0,nj=0;
for(int i=1;i<=m;i++)
{if(c[i]%2==0)os[++no]=c[i];else js[++nj]=c[i];
}
sort(os+1,os+no+1);
sort(js+1,js+nj+1);
for(int i=1;i<=no;i++)
{if(tem>=os[i]/2-1){ans+=os[i]/2;tem-=os[i]/2-1;}else{ans+=tem;tem=0;break;}
}
for(int i=1;i<=nj;i++)
{if(tem>=js[i]/2){ans+=js[i]/2;tem-=js[i]/2;}else {ans+=tem;tem=0;break;}
}
temtemtem 为剩余候选点数量。但是不一定能把 kkk 个点用完,此时 tem>0tem>0tem>0。我们发现把这些点塞进小三角形,破拆掉一些边,并不优于原方案。于是选了的点实际只组成 m+k−temm+k-temm+k−tem 边形,贡献减去 temtemtem。
ans-=tem;
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2e5+9;
ll Q,n,m,k;
ll a[N];
ll c[N],os[N],js[N],no,nj;
int main()
{freopen("cake.in","r",stdin);freopen("cake.out","w",stdout);scanf("%lld",&Q);while(Q--){scanf("%lld%lld%lld",&n,&m,&k);for(int i=1;i<=m;i++)scanf("%lld",&a[i]);if(m+k>=n){printf("%lld\n",n-2);continue;}ll ans=m+k-2;sort(a+1,a+m+1);c[m]=a[1]+n-a[m];for(int i=1;i<m;i++)c[i]=a[i+1]-a[i];/* cout<<"duancha:";for(int i=1;i<=m;i++)cout<<c[i]<<" ";cout<<endl;*/ll tem=k;no=0,nj=0;for(int i=1;i<=m;i++){if(c[i]%2==0)os[++no]=c[i];else js[++nj]=c[i];}sort(os+1,os+no+1);sort(js+1,js+nj+1);for(int i=1;i<=no;i++){if(tem>=os[i]/2-1){ans+=os[i]/2;tem-=os[i]/2-1;}else{ans+=tem;tem=0;break;}}for(int i=1;i<=nj;i++){if(tem>=js[i]/2){ans+=js[i]/2;tem-=js[i]/2;}else {ans+=tem;tem=0;break;}}ans-=tem;printf("%lld\n",ans);}return 0;
}
2.洛谷 P9737 COCI 2022/2023 Lampice
题意
Teo 的阳台是一个长 n+1n+1n+1,宽 m+1m+1m+1 的矩形平台,上有 2k2k2k 盏彩灯,这些彩灯的颜色用 1∼k1 \sim k1∼k 之间的一个数字来表示。每种颜色的彩灯都有 222 盏,它们的坐标都为正整数。
Teo 认为阳台上一个区域是好的,当且仅当:
-
这个小区域是矩形,且边都与阳台的边平行。
-
对于每一种颜色的 222 盏彩灯,要么都在小区域内,要么都在小区域外。
-
小区域的左上角,右下角坐标均为整数。
-
小区域的长宽都至少为 222。
现在,Teo 想请你求出在他的阳台上,有多少个小区域是好的。
注意:左下角坐标为 (0,0)(0,0)(0,0),右上角坐标为 (n,m)(n,m)(n,m)。
1≤n≤1501\le n\le1501≤n≤150,1≤m≤10001\le m\le10001≤m≤1000,0≤k≤2000000\le k\le2000000≤k≤200000。
思路
随机赋权的一个应用。
一个子矩形中只能出现 000 个或 222 个相同颜色的,假如给一个颜色赋权,子矩形的异或和应为 000。
设 (0,0)−(i,j)(0,0)-(i,j)(0,0)−(i,j) 的格子异或和为 Gi,jG_{i,j}Gi,j。如果一个格子上有多个颜色就直接异或上去即可。
于是考虑枚举矩形的上下界 l∼rl\sim rl∼r,让 l∼rl\sim rl∼r 行一起看。bj=Gr,j⊕Gl−1,jb_j=G_{r,j}\oplus G_{l-1,j}bj=Gr,j⊕Gl−1,j,我们考虑一个 bjb_jbj 前面有多少个 bt,t∈[0,j)b_{t},t\in[0,j)bt,t∈[0,j) 使得 bt⊕bj=0b_{t}\oplus b_{j}=0bt⊕bj=0 即可,即宽为 l∼rl\sim rl∼r 行、长为 t+1∼jt+1\sim jt+1∼j 列的矩形合法。
注意矩形的长宽都至少为 222,不仅要控制 l+1≤rl+1\le rl+1≤r,还要去除 t+1=jt+1=jt+1=j 的不合法贡献。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=153,M=1003,K=2e5+9,mod=1234567891234567;
ll n,m,k;
ll G[N][M],val[K],b[M];
int main()
{
// freopen("lam.in","r",stdin);
// freopen("lam.out","w",stdout);scanf("%lld%lld%lld",&n,&m,&k);n++,m++;mt19937 rnd(time(0)),rnd2(time(0));for(int i=1;i<=k;i++){ll xa,ya,xb,yb;scanf("%lld%lld%lld%lld",&xa,&ya,&xb,&yb);xa++,ya++,xb++,yb++;val[i]=rnd()*rnd2()%mod;G[xa][ya]^=val[i];G[xb][yb]^=val[i];}for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)G[i][j]^=G[i-1][j]^G[i][j-1]^G[i-1][j-1];ll ans=0;for(int l=1;l<=n;l++){for(int r=l+1;r<=n;r++){for(int j=1;j<=m;j++)b[j]=G[r][j]^G[l-1][j];for(int j=1;j<=m;j++)ans-=(b[j]==b[j-1]);sort(b+1,b+m+1);ll len=1;b[m+1]=-1;for(int j=1;j<=m+1;j++){if(b[j]!=b[j-1]){ans+=(len-1)*len/2;len=1;}else len++;}}}printf("%lld",ans);return 0;
}