九下四月上旬日记
4.1
闲话
- 中午才换的食堂,从 \(2\) 楼换到 \(3\) 楼了。
- 下午到机房后,没看见 \(miaomiao\) ,但 \(huge\) 和 \(field\) 轮流坐在教师机前。
- 去吃晚饭的路上听见隔壁录播室在录音乐课。
做题纪要
CF573D Bear and Cavalry
luogu P4381 [IOI2008] Island
4.2
闲话
- \(miaomiao\) 回来了。
- 下午 @User-Unauthorized 找 \(miaomiao\) 不知道签了什么东西,然后就回原班了。现在 \(3\) 机房只剩下 @Trump_ 还在补 \(whk\) 了。
- @xrlong 也被“发配”到机房了。
做题纪要
CF1530E Minimax
CF580E Kefa and Watch
-
类似 [ABC331F] Palindrome Query ,线段树维护哈希值即可。
点击查看代码
const ll base=13331,mod=1000000007; ll jc[100010],hs[15][100010]; char s[100010]; struct SegmentTree { ll l,r,sum,len,lazy; }tree[400010]; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void pushup(ll rt) { tree[rt].sum=(tree[lson(rt)].sum*jc[tree[rson(rt)].len]%mod+tree[rson(rt)].sum)%mod; tree[rt].len=tree[lson(rt)].len+tree[rson(rt)].len; } void build(ll rt,ll l,ll r) { tree[rt].l=l; tree[rt].r=r; tree[rt].lazy=10; if(l==r) { tree[rt].sum=s[l]-'0'; tree[rt].len=1; return ; } ll mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void pushdown(ll rt) { if(tree[rt].lazy!=10) { tree[lson(rt)].lazy=tree[rt].lazy; tree[rson(rt)].lazy=tree[rt].lazy; tree[lson(rt)].sum=hs[tree[rt].lazy][tree[lson(rt)].len]; tree[rson(rt)].sum=hs[tree[rt].lazy][tree[rson(rt)].len]; tree[rt].lazy=10; } } void update(ll rt,ll l,ll r,ll val) { if(l<=tree[rt].r&&tree[rt].l<=r) { if(l<=tree[rt].l&&tree[rt].r<=r) { tree[rt].lazy=val; tree[rt].sum=hs[val][tree[rt].len]; return ; } pushdown(rt); update(lson(rt),l,r,val); update(rson(rt),l,r,val); pushup(rt); } } pair<ll,ll> query(ll rt,ll l,ll r) { if(r<tree[rt].l||tree[rt].r<l) { return make_pair(0,0); } if(l<=tree[rt].l&&tree[rt].r<=r) { return make_pair(tree[rt].sum,tree[rt].len); } pushdown(rt); pair<ll,ll>lans=query(lson(rt),l,r),rans=query(rson(rt),l,r); return make_pair((lans.first*jc[rans.second]%mod+rans.first)%mod,lans.second+rans.second); } int main() { ll n,m,k,pd,l,r,val,i,j; cin>>n>>m>>k>>(s+1); for(i=0;i<=n;i++) { jc[i]=(i==0)?1:jc[i-1]*base%mod; } for(i=0;i<=9;i++) { for(j=1;j<=n;j++) { hs[i][j]=(hs[i][j-1]*base%mod+i)%mod; } } build(1,1,n); for(i=1;i<=m+k;i++) { cin>>pd>>l>>r>>val; if(pd==1) { update(1,l,r,val); } else { if(r-l+1==val||query(1,l,r-val).first==query(1,l+val,r).first) { cout<<"YES"<<endl; } else { cout<<"NO"<<endl; } } } return 0; }
4.3
闲话
- 起床后发现下雨了,遂没跑操,直接去吃饭了。
- 一个早读 \(5\) 例通报,给现班主任整急了,把通报的人交出去 \(D\) 了半节多课,让下午象征性地停了 \(20min\) 的奥赛课。
- @wang54321 不幸被通报了。
做题纪要
CF955D Scissors
CF1400F x-prime Substrings
4.4
闲话
- 学校清明不给放假,已经在期待五一学校能放几天了。
- 早上现班主任见 @xrlong 被“发配”到机房了,所以把他桌子和 @wkh2008 放到一起了。也让 @wang54321 从我后面搬到了我左后方,这下我后面没桌子了。
- 晚上现班主任开了小班会。强调了下常规;讲了下曾经学长拿的奖项,“你们现在学的东西高中 whk 不可能学到”,鼓了鼓劲;说周日公开班会一个环节是随机找学生讲历届学长的故事,以及自己是怎么认识/知道他(她)的;说 @wkh2008 和 @xrlong 一直在机房,要看他们期中成绩。
做题纪要
luogu P3809 【模板】后缀排序
-
后缀数组板子。
- 设 \(sa_{i}\) 表示将所有后缀排序后第 \(i\) 小的字符串的起始位置,即后缀数组; \(rk_{i}\) 表示从 \(s_{i \sim |s|}\) 的排名。二者满足 \(sa_{rk_{i}}=rk_{sa_{i}}=i\) 的关系。
- 先对 \(s\) 所有长度为 \(1\) 的子串进行排序,得到 \(sa_{1},rk_{1}\) 。然后进行倍增,具体地,用长度为 \(\frac{w}{2}(2 \le w<n)\) 的子串的排名,即 \(rk_{\frac{w}{2},i},rk_{\frac{w}{2},i+\frac{w}{2}}\) 作为 \(s\) 的第 \(i\) 个长度为 \(w\) 的子串 \(s_{i \sim \min(i+w-1,|s|)}\) 的第一、第二个关键字,并对其进行排序,得到 \(sa_{w},rk_{w}\) ,其中当 \(i+w \ge |s|\) 时,规定 \(rk_{w,i+w}=0\) 。
- 设 \(id_{w,i}\) 表示将所有长度为 \(w\) 的后缀排序后第二关键字第 \(i\) 小的位置。
- 由于 \(rk_{w,i}\) 表示 \(s_{i \sim \min(i+w-1,|s|)}\) 的排名,当 \(w \ge |s|\) 时,得到的 \(sa_{w}\) 即为我们需要的后缀数组 \(sa\) 。
- 将单次
sort
用 基数排序 优化,使单次排序复杂度由 \(O(n \log{n})\) 变为 \(O(n)\) 。 - 总时间复杂度为 \(O(n \log{n})\) 。
- 由于基数排序常数巨大,需要进行一定的常数优化。
- 考虑第二关键字 \(rk_{\frac{w}{2},i+\frac{w}{2}}\) 排序的实质,是将超出字符串范围的放到前面,剩下的按照原顺序放回。
- 若排名都不相同可直接生成后缀数组
- 对于新的 \(rk\) ,若排名都不相同,即值域为 \([1,|s|]\) ,则可以直接生成后缀数组。
- 优化计数排序的值域
- 减少不连续内存访问
点击查看代码
int sa[1000010],rk[2000010],oldrk[2000010],id[1000010],cnt[1000010],key[1000010]; char s[1000010]; int val(char x) { return (int)x; } void counting_sort(int n,int m) { memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) { cnt[key[i]]++; } for(int i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(int i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init_sa(char s[],int len) { int m=127,tot=0,num=0,i,w; for(i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(w=1;tot!=len;w<<=1,m=tot)//m=tot 用来优化计数排序的值域 //之所以不写 w<n ,是因为要确保长度为 1 的串进行一次后缀排序 { num=0; for(i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(i=1;i<=len;i++) { key[i]=rk[id[i]];//将 rk[id[i]] 存到 key[i] 里,来减少不连续内存访问,以达到卡常的效果 } counting_sort(len,m); for(i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } } int main() { int n,i; cin>>(s+1); n=strlen(s+1); init_sa(s,n); for(i=1;i<=n;i++) { cout<<sa[i]<<" "; } return 0; }
luogu P4051 [JSOI2007] 字符加密
-
考虑破环为链,将 \(s\) 复制一遍,得到 \(ss\) 。
-
手摸样例,得到后缀排序如下,有
I0O7SJ
分别出现在排名为 \(2,4,6,8,10,12\) 的第 \(6\) 个位置。点击查看后缀排序
1:07 2:07JSOI07 3:7 4:7JSOI07 5:I07 6:I07JSOI07 7:JSOI07 8:JSOI07JSOI07 9:OI07 10:OI07JSOI07 11:SOI07 12:SOI07JSOI07
-
分别取 \(sa_{i} \le |s|\) 的 \(s_{sa_{i}+|s|-1}\) 即可。
点击查看代码
int sa[200010],rk[400010],oldrk[400010],id[200010],cnt[200010],key[200010]; char s[200010]; int val(char x) { return (int)x; } void counting_sort(int n,int m) { memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) { cnt[key[i]]++; } for(int i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(int i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init_sa(char s[],int len) { int m=127,tot=0,num=0,i,w; for(i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(w=1;tot!=len;w<<=1,m=tot) { num=0; for(i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } } int main() { int n,i; cin>>(s+1); n=strlen(s+1); for(i=1;i<=n;i++) { s[i+n]=s[i]; } n*=2; init_sa(s,n); for(i=1;i<=n;i++) { if(sa[i]<=n/2) { cout<<s[sa[i]+n/2-1]; } } return 0; }
UVA1223 Editor
-
求 \(height\) 板子。
- 设 \(LCP(x,y)\) 表示字符串 \(x\) 和 \(y\) 的最长公共前缀的长度。
- \(LCP\) 相关性质
- 性质 \(1\) : \(LCP(s_{x \sim |s|},s_{y \sim |s|})=\min\limits_{x \le i<j<k \le y} \{ LCP(s_{i \sim |s|},s_{j \sim |s|}),LCP(s_{j \sim |s|},s_{k \sim |s|}) \}\) 。
- 证明
- 设 \(\min\{ LCP(s_{i \sim |s|},s_{j \sim |s|}),LCP(s_{j \sim |s|},s_{k \sim |s|}) \}=p>0\) ,则有 \(s_{i+l}=s_{j+l}=s_{k+l}(0 \le l \le p-1)\) 。
- 容易有 \(LCP(s_{i \sim |s|},s_{k \sim |s|})=p\) ,进一步可以扩展到 \(LCP(s_{x \sim |s|},s_{y \sim |s|})\) 。
- \(LCP(s_{i \sim |s|},s_{k \sim |s|})=p\) 可通过反证法证明。
- 证明
- 性质 \(2\) : \(\begin{cases} LCP(s_{sa_{x} \sim |s|},s_{sa_{y} \sim |s|})=\min\limits_{i=x+1}^{y} \{ LCP(s_{sa_{i} \sim |s|},s_{sa_{i-1} \sim |s|}) \}=\min\limits_{i=x+1}^{y} \{ height_{i} \} \\ LCP(s_{x \sim |s|},s_{y \sim |s|})=\min\limits_{i=rk_{x}+1}^{rk_{y}} \{ LCP(s_{sa_{i} \sim |s|},s_{sa_{i-1} \sim |s|}) \}=\min\limits_{i=rk_{x}+1}^{rk_{y}} \{ height_{i} \} \end{cases}\) 。
- 证明看 OI WiKi 吧。
- 性质 \(1\) : \(LCP(s_{x \sim |s|},s_{y \sim |s|})=\min\limits_{x \le i<j<k \le y} \{ LCP(s_{i \sim |s|},s_{j \sim |s|}),LCP(s_{j \sim |s|},s_{k \sim |s|}) \}\) 。
- \(LCP\) 相关性质
- 设 \(height_{i}\) 表示 \(LCP(s_{sa_{i} \sim |s|},s_{sa_{i-1} \sim |s|})\) ,即第 \(i\) 名的后缀和第 \(i-1\) 名的后缀的最长公共前缀的长度。特别地,有 \(height_{1}=0\) 。
- \(O(n)\) 求 \(height\) 需要的结论: \(height_{rk_{i}} \ge height_{rk_{i-1}}-1\) 。故求 \(height_{rk_{i}}\) 时继承 \(height_{rk_{i-1}}-1\) 得到下限即可。
- 证明看 OI Wiki 吧。
- 设 \(LCP(x,y)\) 表示字符串 \(x\) 和 \(y\) 的最长公共前缀的长度。
-
最终,有 \(\max\limits_{i=1}^{|s|} \{ height_{i} \}\) 即为所求。
点击查看代码
int sa[5010],rk[10010],oldrk[10010],id[5010],cnt[5010],key[5010],height[5010]; char s[5010]; int val(char x) { return (int)x; } void counting_sort(int n,int m) { memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) { cnt[key[i]]++; } for(int i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(int i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init_sa(char s[],int len) { int m=127,tot=0,num=0,i,j,w; for(i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(w=1;tot!=len;w<<=1,m=tot) { num=0; for(i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } for(i=1,j=0;i<=len;i++) { j-=(j>=1); while(s[i+j]==s[sa[rk[i]-1]+j])//前面不写成 sa[rk[i]] 来减少不连续内存访问 { j++; } height[rk[i]]=j; } } int main() { int t,n,ans,i,j; cin>>t; for(j=1;j<=t;j++) { ans=0; cin>>(s+1); n=strlen(s+1); init_sa(s,n); for(i=1;i<=n;i++) { ans=max(ans,height[i]); } cout<<ans<<endl; } return 0; }
4.5
闲话
- @wkh2008 和 @xrlong 被现班主任拉来跑早操了,但课间操还是不跑。
- 早上 \(miaomiao\) 问我们第一节课到底是啥,问我们当时咋换的课。上完第一节课回去的路上碰见了 @Vsinger_洛天依 ,称之为“交换场地”。
- 上午现教处见学校公众号没素材了,就趁我们上体育课拍了点素材发公众号了,我、 @Charlie_ljk 、 @Pursuing_OIer 被拍了。
- \(miaomiao\) 下午 \(13:50 \sim 18:05\) 安排了一场模拟赛。
- 用 \(VScode\) 的
Competitive Programming Helper (cph)
插件测了几发大样例,感觉还行,懒得在终端敲diff filename1.xxx filename2.xxx
了。
做题纪要
luogu P2852 [USACO06DEC] Milk Patterns G
-
一个子串出现至少 \(k\) 次等价于有至少连续 \(k\) 个后缀以这个子串作为公共前缀。
-
最终,有 \(\max\limits_{i=k-1}^{|s|} \{ \min\limits_{j=i-(k-1)}^{i} \{ height_{j} \} \}\) 即为所求,单调队列维护即可。
点击查看代码
int s[1000010],sa[1000010],rk[2000010],oldrk[2000010],id[1000010],cnt[1000010],key[1000010],height[1000010]; deque<int>q; void counting_sort(int n,int m) { memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) { cnt[key[i]]++; } for(int i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(int i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init_sa(int s[],int len) { int m=1000000,tot=0,num=0,i,j,w; for(i=1;i<=len;i++) { rk[i]=s[i]; id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(w=1;tot!=len;w<<=1,m=tot) { num=0; for(i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } for(i=1,j=0;i<=len;i++) { j-=(j>=1); while(s[i+j]==s[sa[rk[i]-1]+j]) { j++; } height[rk[i]]=j; } } int main() { int n,k,ans=0,i; cin>>n>>k; k--; for(i=1;i<=n;i++) { cin>>s[i]; } init_sa(s,n); for(i=1;i<=n;i++) { while(q.empty()==0&&height[q.front()]>=height[i]) { q.pop_front(); } q.push_front(i); while(q.empty()==0&&q.back()<=i-k) { q.pop_back(); } if(i>=k) { ans=max(ans,height[q.back()]); } } cout<<ans<<endl; return 0; }
luogu P4248 [AHOI2013] 差异
-
推式子,有 \(\begin{aligned} \sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}|s_{i \sim |s|}|+|s_{j \sim |s|}| &=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}(n-i+1)+(n-j+1) \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}2(n+1)-\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}i-\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}j \\ &=\sum\limits_{i=1}^{n}2(n+1)(n-i)-\sum\limits_{i=1}^{n}i(n-i)-\sum\limits_{i=1}^{n}i(i-1) \\ &=2(n+1)\sum\limits_{i=1}^{n}(n-i)-(n-1)\sum\limits_{i=1}^{n}i \\&=n(n+1)(n-1)-\frac{n(n+1)(n-1)}{2} \\ &=\frac{n(n+1)(n-1)}{2} \end{aligned}\) 。
-
由 \(LCP\) 性质 \(2\) ,有 \(LCP(s_{sa_{i} \sim |s|,sa_{j} \sim |s|})=\min\limits_{k=i+1}^{j} \{ height_{k} \}\) 。然后有 \(\begin{aligned} \sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}LCP(s_{i \sim |s|,j \sim |s|}) &=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}LCP(s_{sa_{i} \sim |s|,sa_{j} \sim |s|}) \\&=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}\min\limits_{k=i+1}^{j} \{ height_{k} \} \\ &=\sum\limits_{i=2}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \} \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \}-\sum\limits_{i=1}^{n}\min\limits_{j=1}^{i} \{ height_{j} \} \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \}-\sum\limits_{i=1}^{n}0 \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \} \end{aligned}\) 。
-
\(\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \}\) 可用单调栈维护。
- 具体实现详见 普及模拟1 T1 Past 。
点击查看代码
ll sa[500010],rk[1000010],oldrk[1000010],id[500010],cnt[500010],key[500010],height[500010],f[500010]; char s[500010]; stack<ll>st; ll val(char x) { return (ll)x; } void counting_sort(ll n,ll m) { memset(cnt,0,sizeof(cnt)); for(ll i=1;i<=n;i++) { cnt[key[i]]++; } for(ll i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(ll i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init_sa(char s[],ll len) { ll m=127,tot=0,num=0,i,j,w; for(i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(w=1;tot!=len;w<<=1,m=tot) { num=0; for(i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } for(i=1,j=0;i<=len;i++) { j-=(j>=1); while(s[i+j]==s[sa[rk[i]-1]+j]) { j++; } height[rk[i]]=j; } } int main() { ll n,sum=0,i; cin>>(s+1); n=strlen(s+1); init_sa(s,n); for(i=1;i<=n;i++) { while(st.empty()==0&&height[st.top()]>=height[i]) { st.pop(); } if(st.empty()==0) { f[i]=f[st.top()]+height[i]*(i-st.top()); } else { f[i]=f[0]+height[i]*(i-0); } st.push(i); sum+=f[i]; } cout<<n*(n+1)*(n-1)/2-2*sum<<endl; return 0; }
tgHZOJ 5663.最后一课
- 详见 初三奥赛模拟测试4 T1 最后一课 。
luogu P2408 不同子串个数
-
多倍经验: SP694 DISUBSTR - Distinct Substrings | SP705 SUBST1 - New Distinct Substrings
-
对于两个子串,其重复的前缀数等于其最长公共前缀的长度。对于一个后缀 \(s_{sa_{i} \sim n}\) ,它产生了 \(n-sa_{i}+1\) 个前缀,和 \(s_{sa_{i-1} \sim n}\) 相比产生了 \(height_{i}\) 个相同的前缀,则会产生 \(n-sa_{i}+1-height_{i}\) 个不同的子串。
-
故最终 \(\begin{aligned} \sum\limits_{i=1}^{n}n-sa_{i}+1-height_{i} &=n(n+1)-\sum\limits_{i=1}^{n}sa_{i}-\sum\limits_{i=1}^{n}height_{i} \\ &=n(n+1)-\frac{n(n+1)}{2}-\sum\limits_{i=1}^{n}height_{i} \\ &=\frac{n(n+1)}{2}-\sum\limits_{i=1}^{n}height_{i} \end{aligned}\) 即为所求。
点击查看代码
ll sa[100010],rk[200010],oldrk[200010],id[100010],cnt[100010],key[100010],height[100010],a[100010],b[100010],fminn[100010][20]; char s[100010]; ll val(char x) { return (ll)x; } void counting_sort(ll n,ll m) { memset(cnt,0,sizeof(cnt)); for(ll i=1;i<=n;i++) { cnt[key[i]]++; } for(ll i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(ll i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init_sa(char s[],ll len) { ll m=127,tot=0,num=0,i,j,w; for(i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(w=1;tot!=len;w<<=1,m=tot) { num=0; for(i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } for(i=1,j=0;i<=len;i++) { j-=(j>=1); while(s[i+j]==s[sa[rk[i]-1]+j]) { j++; } height[rk[i]]=j; } } int main() { ll n,sum=0,i; cin>>n>>(s+1); init_sa(s,n); for(i=1;i<=n;i++) { sum+=height[i]; } cout<<n*(n+1)/2-sum<<endl; return 0; }
AT_s8pc_2_e 部分文字列
-
对于一个后缀 \(s_{sa_{i} \sim n}\) ,它产生了 \(n-sa_{i}+1\) 个前缀,其长度和为 \(\frac{(n-sa_{i}+1)(n-sa_{i}+2)}{2}\) ;和 \(s_{sa_{i-1} \sim n}\) 相比产生了 \(height_{i}\) 个相同的前缀,其长度和为 \(\frac{height_{i}(height_{i}+1)}{2}\) 。
-
最终,有 \(\begin{aligned} \sum\limits_{i=1}^{n}\frac{(n-sa_{i}+1)(n-sa_{i}+2)}{2}-\sum\limits_{i=1}^{n}\frac{height_{i}(height_{i}+1)}{2} &=\sum\limits_{i=1}^{n}\frac{(n-i+1)(n-i+2)}{2}-\sum\limits_{i=1}^{n}\frac{height_{i}(height_{i}+1)}{2} \\ &=\sum\limits_{i=1}^{n}\frac{i(i+1)}{2}-\sum\limits_{i=1}^{n}\frac{height_{i}(height_{i}+1)}{2} \\ &=\frac{1}{2} \times (\frac{n(n+1)(2n+1)}{6}+\frac{n(n+1)}{2})-\sum\limits_{i=1}^{n}\frac{height_{i}(height_{i}+1)}{2} \end{aligned}\) 即为所求。
点击查看代码
ll sa[100010],rk[200010],oldrk[200010],id[100010],cnt[100010],key[100010],height[100010],a[100010],b[100010],fminn[100010][20]; char s[100010]; ll val(char x) { return (ll)x; } void counting_sort(ll n,ll m) { memset(cnt,0,sizeof(cnt)); for(ll i=1;i<=n;i++) { cnt[key[i]]++; } for(ll i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(ll i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init_sa(char s[],ll len) { ll m=127,tot=0,num=0,i,j,w; for(i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(w=1;tot!=len;w<<=1,m=tot) { num=0; for(i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } for(i=1,j=0;i<=len;i++) { j-=(j>=1); while(s[i+j]==s[sa[rk[i]-1]+j]) { j++; } height[rk[i]]=j; } } int main() { ll n,sum=0,i; scanf("%s",s+1); n=strlen(s+1); init_sa(s,n); for(i=1;i<=n;i++) { sum+=height[i]*(height[i]+1)/2; } cout<<(n*(n+1)*(2*n+1)/6+n*(n+1)/2)/2-sum<<endl; return 0; }
4.6
闲话
- 因高一要远足,早上占了大操场,被迫去小操场跑操,要求的脚步忽快忽慢,难崩;下午占了大操场,初中的都去小操场放假。
- 第 \(1\) 节课前现班主任说因放假占了下午一节课,所以上午第 \(5\) 节课最好上奥赛自习,然后第 \(4\) 节课 @lty_ylzsx 直接就把“公”改成了“A”,我们直接就去机房了。到机房后 \(miaomiao\) 问了我们明天什么时间返校。
- 因桌子旁边太多书被现班主任 \(D\) 了。
- 下午到机房后,发现所有人又双叒叕被关机了。后因 @wkh2008 等人过于“放飞自我”被 \(miaomiao\) \(D\) 了。
做题纪要
tgHZOJ 5664. 日常
- 详见 初三奥赛模拟测试4 T2 日常 。
tgHZOJ 5665. 渡尘
- 详见 初三奥赛模拟测试4 T3 渡尘 。
4.7
闲话
- 下午返校后收拾了下东西,然后直接来机房了,到机房后发现没有 \(miaomiao\) 。
- 因说话声音过大,被 \(feifei\) \(D\) 了,“好好做题啊,不要总说话”。
- @STA_Morlin 限时返厂,想来机房用【数据删除】进行 【数据删除】 ,但因为有 【数据删除】 ,所以又拿着 【数据删除】 走了。
- \(16:30\) 时 \(miaomiao\) 使用前方高级摄像头隔空喊话,称现班主任找我们,然后让我们回班。回班后被现班主任 \(D\) 了,称没有教练去机房怎么保证学习效率,以后再有这事就停我们课(因为不是第一次了)。
- 晚上开公开班会,原班主任一进来就问我坐在哪里,然后坐在了我后面,问我期中打算考多少,还看见我书下面压的《西游记》了。开班会的时候有几个互动的环节,包括但不限于统计中午留下学习的、递交并随机找人念挑战书,尬死我了。班会上说了下我们对于年级、学校的引领等作用,喂了点鸡汤;给各班主任说我们平常上(奥赛)课非常专注,奥赛三节连堂属于正常现象。
做题纪要
4.8
闲话
- 貌似 \(miaomiao\) 还没回来。
- 下午 \(field\) 说周日 \(13:00 \sim 18:00\) 有北京交通大学第十八届大学生程序设计竞赛(线上同步赛),让我们注册了账号,高校和院校填的 HZ ,到时候统一打。同时开了会全网,存了下 luogu 的 cookie 。
邀请各位兄弟院校新生友情参赛!
这下 HZ 成北交大兄弟院校了。- 比赛链接
- @Pursuing_OIer 翻程序设计评测常见问题及说明时翻到了
本站评测环境未开启忽略行末空格和文件尾换行,因此如果答案正确,但格式存在问题则会出现Presentation Error的评测结果。请注意严格按照题目的要求输入输出。
,有点抽象。
做题纪要
luogu P6066 [USACO05JAN] Watchcow S
-
无向图欧拉回路板子。
点击查看代码
int id[100010]; vector<pair<int,int> >e[100010]; stack<int>s; void dfs(int x) { for(int i=id[x];i<e[x].size();i=max(i+1,id[x])) { if(e[x][i].second==0) { id[x]=i+1; e[x][i].second=1; dfs(e[x][i].first); } } s.push(x); } int main() { int n,m,u,v,i; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u>>v; e[u].push_back(make_pair(v,0)); e[v].push_back(make_pair(u,0)); } dfs(1); while(s.empty()==0) { cout<<s.top()<<endl; s.pop(); } return 0; }
luogu P2659 美丽的序列
-
设 \([l_{i},r_{i}]\) 表示满足 \(\min\limits_{k=l_{i}}^{r_{i}} \{ a_{ k} \}=a_{i}\) 的最大区间,单调栈维护即可。
点击查看代码
ll a[2000010],l[2000010],r[2000010]; stack<ll>s1,s2; int main() { ll n,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=n;i++) { l[i]=i; while(s1.empty()==0&&a[s1.top()]>=a[i]) { l[i]=l[s1.top()]; s1.pop(); } s1.push(i); } for(i=n;i>=1;i--) { r[i]=i; while(s2.empty()==0&&a[s2.top()]>=a[i]) { r[i]=r[s2.top()]; s2.pop(); } s2.push(i); } for(i=1;i<=n;i++) { ans=max(ans,(r[i]-l[i]+1)*a[i]); } cout<<ans<<endl; return 0; }
tgHZOJ 5666. 罪人挽歌
- 详见 初三奥赛模拟测试4 T4 罪人挽歌 。
4.9
闲话
- 早上起床后发现昨天晚上下雨了,跑道是湿的,没办法跑操了,但还是要去操场集合、候操、听中考动员。
- 上午物理老师说因周日正常周测所以期中提前到了周三晚上,但这周六没有家长会,体活改学生大会,周日上午第 \(5\) 节改体活。数学老师说昨天主任开会的时候说奥赛生 \(whk\) 不能掉太多,中考还指望我们出状元呢。
- 下午到机房后发现 \(miaomiao\) 回来了。 \(miaomiao\) 看模拟赛 \(T4\) 大部分人都过了,就不组织讲题了,让不会的人找别人问问。
- 隔壁录播室又在录课, \(huge\) 过来维持了下秩序。
做题纪要
luogu P5546 [POI2000] 公共串
-
多倍经验: SP1811 LCS - Longest Common Substring | SP1812 LCS2 - Longest Common Substring II | SP10570 LONGCS - Longest Common Substring
-
将 \(s_{1} \sim s_{n}\) 拼起来得到 \(t=s_{1}s'_{1}s_{2}s'_{2} \dots s_{n}s'_{n}\) ,其中 \(s'\) 表示分隔符,且 \(s'_{1}<s'_{2}<\dots<s'_{n}\) 。由 luogu P2852 [USACO06DEC] Milk Patterns G ,有 \(\max\limits_{1 \le l<r \le |t|} \{ \min\limits_{i=l+1}^{r} \{ height_{i} \} \}\) 即为所求,其中排名在 \([l,r]\) 中的后缀包含了所有字符串,即对于任意一个 \(s_{i}\) 均有一个后缀的排名在 \([l,r]\) 中 。用双指针加单调队列维护。
点击查看代码
int sa[11000],rk[21000],oldrk[21000],id[11000],cnt[11000],key[11000],height[11000],L[11000],R[11000],c[11000],vis[11000]; char s[11000],t[11000]; deque<int>q; int val(int x) { return (char)x; } void counting_sort(int n,int m) { memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) { cnt[key[i]]++; } for(int i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(int i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init_sa(char s[],int len) { int m=127,tot=0,num=0,i,j,w; for(i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(w=1;tot!=len;w<<=1,m=tot) { num=0; for(i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } for(i=1,j=0;i<=len;i++) { j-=(j>=1); while(s[i+j]==s[sa[rk[i]-1]+j]) { j++; } height[rk[i]]=j; } } void add(int l,int &sum) { if(c[l]!=0)//特判分隔符 { vis[c[l]]++; sum+=(vis[c[l]]==1); } } void del(int l,int &sum) { if(c[l]!=0) { vis[c[l]]--; sum-=(vis[c[l]]==0); } } int main() { int m,n=0,ans=0,sum=0,l,r,i,j; cin>>m; for(i=1;i<=m;i++) { cin>>(t+1); L[i]=n+1; for(j=1;j<=strlen(t+1);j++) { n++; s[n]=t[j]; } R[i]=n; n++; s[n]=i+'0';//分隔符视字符集而定 } init_sa(s,n); for(i=1;i<=m;i++) { for(j=L[i];j<=R[i];j++) { c[rk[j]]=i; } } add(1,sum); for(l=1,r=2;r<=n;r++) { while(q.empty()==0&&height[q.front()]>=height[r]) { q.pop_front(); } q.push_front(r); add(r,sum); if(sum==m) { while(sum==m&&l<=r-1) { del(l,sum); l++; } l--; add(l,sum); } while(q.empty()==0&&q.back()<=l) { q.pop_back(); } if(sum==m) { ans=max(ans,height[q.back()]); } } cout<<ans<<endl; return 0; }
luogu P2463 [SDOI2008] Sandy 的卡片
-
差分后就转化成了 luogu P5546 [POI2000] 公共串 。
-
由于得到的差分序列长度等于原串长度减一,所以最终统计答案的时候要加回来。
点击查看代码
int s[2100000],t[2100000],sa[2100000],rk[4100000],oldrk[4100000],id[2100000],cnt[2100000],key[2100000],height[2100000],L[2100000],R[2100000],c[2100000],vis[2100000]; deque<int>q; int val(int x) { return (int)x; } void counting_sort(int n,int m) { memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) { cnt[key[i]]++; } for(int i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(int i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init_sa(int s[],int len) { int m=5000,tot=0,num=0,i,j,w; for(i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(w=1;tot!=len;w<<=1,m=tot) { num=0; for(i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } for(i=1,j=0;i<=len;i++) { j-=(j>=1); while(s[i+j]==s[sa[rk[i]-1]+j]) { j++; } height[rk[i]]=j; } } void add(int l,int &sum) { if(c[l]!=0) { vis[c[l]]++; sum+=(vis[c[l]]==1); } } void del(int l,int &sum) { if(c[l]!=0) { vis[c[l]]--; sum-=(vis[c[l]]==0); } } int main() { int m,mm,n=0,ans=0,sum=0,l,r,i,j; cin>>m; for(i=1;i<=m;i++) { cin>>mm; L[i]=n+1; for(j=1;j<=mm;j++) { cin>>t[j]; if(j>=2) { n++; s[n]=t[j]-t[j-1]; } } R[i]=n; n++; s[n]=1864+i; } for(i=1;i<=n;i++) { s[i]+=1864;//保证 s[i] 不为负数,防止 RE } init_sa(s,n); for(i=1;i<=m;i++) { for(j=L[i];j<=R[i];j++) { c[rk[j]]=i; } } add(1,sum); for(l=1,r=2;r<=n;r++) { while(q.empty()==0&&height[q.front()]>=height[r]) { q.pop_front(); } q.push_front(r); add(r,sum); if(sum==m) { while(sum==m&&l<=r-1) { del(l,sum); l++; } l--; add(l,sum); } while(q.empty()==0&&q.back()<=l) { q.pop_back(); } if(sum==m) { ans=max(ans,height[q.back()]); } } cout<<ans+1<<endl; return 0; }
4.10
闲话
- 体育课后是历史,历史课前,历史老师说上周班会他听得心潮澎湃的,班会上说我们上课非常专注,为啥课前的课件这么吵。
- 下午到机房后发现隔壁录播室在录高二语文课,找了些高二的来听。
- \(miaomiao\) 在教师机坐了一会就走了,然后换成了 \(field\) 坐在前面。
- \(field\) 催了催做 vjudge 的题。
- 因晚上要考期中,所以下午第 \(10\) 节课回班收拾考场。
- 晚上是语文和公共自习。
- 语文
“黄发垂髫”代指小孩和老人
的说法错误,“黄发垂髫”代指老人和小孩
的说法错误。一句话概括主要内容
真的是要“一句话”,必须是纯主谓宾的结构,不能有并列分句。- 文言文是苏轼的《和桃源诗序》,因学校的高级印刷机,把
醯
印成了酸
,然后就有了生不识盐酸
。 - 记叙文一个概括内容的一问的标准答案为
祖母用扇子为“我”驱蚊
,但这部分在原文仅出现了一句话,绷不住。
- 语文
做题纪要
luogu P3181 [HAOI2016] 找相同字符
-
设 \(f(s)\) 表示 \(s\) 中位置不同大小相同的子串个数,由 luogu P4248 [AHOI2013] 差异 有 \(f(s)=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}LCP(s_{i \sim |s|},s_{j \sim |s|})\) 。
-
最终,有 \(f(s_{1} \& s_{2})-f(s_{1})-f(s_{2})\) 即为所求,其中 \(\&\) 为分隔符。
点击查看代码
ll sa[400010],rk[800010],oldrk[800010],id[400010],cnt[400010],key[400010],height[400010],f[400010]; char s1[400010],s2[400010],s[400010]; ll val(char x) { return (ll)x; } void counting_sort(ll n,ll m) { memset(cnt,0,sizeof(cnt)); for(ll i=1;i<=n;i++) { cnt[key[i]]++; } for(ll i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(ll i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init_sa(char s[],ll len) { ll m=127,tot=0,num=0,i,j,w; for(i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(w=1;tot!=len;w<<=1,m=tot) { num=0; for(i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } for(i=1,j=0;i<=len;i++) { j-=(j>=1); while(s[i+j]==s[sa[rk[i]-1]+j]) { j++; } height[rk[i]]=j; } } ll ask(char s[],ll len) { ll sum=0,i; stack<ll>st; init_sa(s,len); for(i=1;i<=len;i++) { while(st.empty()==0&&height[st.top()]>=height[i]) { st.pop(); } if(st.empty()==0) { f[i]=f[st.top()]+height[i]*(i-st.top()); } else { f[i]=f[0]+height[i]*(i-0); } st.push(i); sum+=f[i]; } return sum; } int main() { ll n1,n2,n=0,i; cin>>(s1+1)>>(s2+1); n1=strlen(s1+1); n2=strlen(s2+1); for(i=1;i<=n1;i++) { n++; s[n]=s1[i]; } n++; s[n]='0'; for(i=1;i<=n2;i++) { n++; s[n]=s2[i]; } cout<<ask(s,n)-ask(s1,n1)-ask(s2,n2)<<endl; return 0; }
CF1073G Yet Another LCP Problem
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18109027,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。