2024寒假年前集训日记
1.27
闲话
-
下午期末家长会的时候,以为家长还没到,于是就在外面等了 \(30min+\) ,等不及了发现家长已经进去了,然后班主任也已经开始讲了,乐。
-
吃完晚饭后,搬宿舍到了 \(1201\) 。
-
晚上, \(huge\) 关于不要忽略算法的细节的讲话。
点击查看讲话
咱这次集训哈后面会安排放视频,学过的没学过的都建议听一下,看自己当时学这个知识点的时候就没有什么细节被忽略了。差分约束大家都学了吧,应该都做了 小K的农场 那题了吧。那题是卡基于 BFS 实现的 SPFA,我就看了眼lrb的代码,他是拿双端队列优化后的 SPFA 过的,其他人通过的我也不知道用什么方式过的。你们有拿 DFS 实现的 SPFA 吗?要是没有的话,你们可以学一下,这题 DFS 实现的 SPFA 跑得飞快。
做题纪要
CF1433E Two Round Dances
CF739A Alyona and mex
1.28
闲话
- 上午放的 \(hash\) 视频,但我打了一上午的差分约束,课基本没听。
- 下午放的 \(KMP\) 视频。
- 晚上,【数据删除】半小时内连续被 \(huge\) 两次抓到摸鱼,于是【数据删除】以一己之力让 \(1\) 机房洛谷被禁了。然后 \(huge\) 讲了点东西,详见 2024寒假集训纪要 1.28 部分。
- 晚上下课后,因时间不够了,故跟着 @xrlong 和@wkh2008 在小操场跑了一圈,最后和他们差出了 \(50m\) 。
做题纪要
luogu P1993 小 K 的农场
- 详见 【学习笔记】差分约束 。
luogu P3275 [SCOI2011] 糖果
- 详见 【学习笔记】差分约束 。
LibreOJ 10033. 「一本通 2.1 例 1」Oulipo
-
\(hash\) 板子。
点击查看代码
const ll mod=998244353,base=13331; ll a[1000002],b[1000002],jc[1000002]; char s1[1000002],s2[1000002]; void sx_hash(char s[],ll a[],ll len) { for(ll i=0;i<=len;i++) { a[i]=(i==0)?0:((a[i-1]*base%mod+s[i])%mod); } } ll ask_hash(ll a[],ll l,ll r) { return (a[r]-a[l-1]*jc[r-l+1]%mod+mod)%mod; } int main() { ll t,len1,len2,ans,i,j; cin>>t; for(i=1;i<=t;i++) { cin>>(s1+1)>>(s2+1); ans=0; len1=strlen(s1+1); len2=strlen(s2+1); for(j=0;j<=max(len1,len2);j++) { jc[j]=(j==0)?1:(jc[j-1]*base%mod); } sx_hash(s1,a,len1); sx_hash(s2,b,len2); for(j=1;j+len1-1<=len2;j++) { ans+=(ask_hash(a,1,len1)==ask_hash(b,j,j+len1-1)); } cout<<ans<<endl; } return 0; }
luogu P3370【模板】字符串哈希
-
\(hash\) 板子。
点击查看代码
const ll mod=998244353,base=13331; ll a[20000],jc[20000]; char s[2001]; ll sx_hash(char s[],ll len) { ll ans; for(ll i=0;i<=len;i++) { ans=(i==0)?0:((ans*base%mod+s[i])%mod); } return ans; } int main() { ll n,ans=1,i; cin>>n; for(i=1;i<=n;i++) { cin>>(s+1); a[i]=sx_hash(s,strlen(s+1)); } sort(a+1,a+1+n); for(i=1;i<=n-1;i++) { ans+=(a[i]!=a[i+1]); } cout<<ans<<endl; return 0; }
luogu P3667 [USACO17OPEN] Bovine Genomics G
-
显然,区间长度具有单调性。考虑二分长度,然后把 \(hash\) 值丢到一个
map
里面,进行判断。- 容易出错的点:对于一段区间 \([l,r]\) ,只有对于所有的 \(i(1 \le i \le n)\) 均有 \(\sum\limits_{j=1}^{n}[A_{i_{l}} \sim A_{i_{r}} \ne B_{j_{l}} \sim B_{j_{r}}]=n\) 才满足题意。
-
需要双哈希。
点击查看代码
const ll mod1=998244353,mod2=1000000007,base=13331; ll a[1002][3][1002],b[1002][3][1002],jc[3][1002]; char s1[1002],s2[1002]; void sx_hash(char s[],ll a[],ll len,ll mod) { for(ll i=0;i<=len;i++) { a[i]=(i==0)?0:((a[i-1]*base%mod+s[i])%mod); } } ll ask_hash(ll a[],ll jc[],ll l,ll r,ll mod) { return (a[r]-a[l-1]*jc[r-l+1]%mod+mod)%mod; } bool check(ll mid,ll n,ll m) { ll i,l,r,flag; for(l=1,r=l+mid-1;r<=m;l++,r++) { map<ll,bool>vis[3]; flag=0; for(i=1;i<=n;i++) { vis[1][ask_hash(a[i][1],jc[1],l,r,mod1)]=true; vis[2][ask_hash(a[i][2],jc[2],l,r,mod2)]=true; } for(i=1;i<=n;i++) { if(vis[1].find(ask_hash(b[i][1],jc[1],l,r,mod1))!=vis[1].end()&&vis[2].find(ask_hash(b[i][2],jc[2],l,r,mod2))!=vis[2].end()) { flag=1; break; } } if(flag==0) { return false; } } return true; } int main() { ll n,m,l=1,r,ans,mid,i; cin>>n>>m; r=m; for(i=0;i<=m;i++) { jc[1][i]=(i==0)?1:(jc[1][i-1]*base%mod1); jc[2][i]=(i==0)?1:(jc[2][i-1]*base%mod2); } for(i=1;i<=n;i++) { cin>>(s1+1); sx_hash(s1,a[i][1],m,mod1); sx_hash(s1,a[i][2],m,mod2); } for(i=1;i<=n;i++) { cin>>(s2+1); sx_hash(s2,b[i][1],m,mod1); sx_hash(s2,b[i][2],m,mod2); } while(l<=r) { mid=(l+r)/2; if(check(mid,n,m)==true) { l=mid+1; } else { ans=mid; r=mid-1; } } cout<<ans<<endl; return 0; }
luogu P4503 [CTSC2014] 企鹅 QQ
-
考虑枚举不同的位置,然后将前后的 \(hash\) 值再次进行 \(hash\) 然后拼起来。
-
因
map
常数过大,在找一个数在区间内出现的次数时,使用sort
挨个枚举进行判断,而不是使用map
存储。 -
这题卡 \(hash\) 的模数,故选择
unsigned long long
的自然溢出和rand()
的随机数来作为模数。点击查看代码
const ull base=13331; ull a[30002][300],aa[30002],jc[70002]; char s[300],ss[300]; void sx_hash(char s[],ull a[],ull len) { for(ull i=0;i<=len;i++) { a[i]=(i==0)?0:(a[i-1]*base+s[i]); } } ull ask_hash(ull a[],ull l,ull r) { return a[r]-a[l-1]*jc[r-l+1]; } int main() { ull n,m,ss,ans=0,sum,mod1,mod2,i,j; cin>>n>>m>>ss; srand(time(0)); mod1=rand(); mod2=rand(); for(i=0;i<=m;i++) { jc[i]=(i==0)?1:(jc[i-1]*base); } for(i=1;i<=n;i++) { cin>>(s+1); sx_hash(s,a[i],m); } for(i=1;i<=m;i++) { for(j=1;j<=n;j++) { aa[j]=ask_hash(a[j],1,i-1)*mod1+ask_hash(a[j],i+1,m)*mod2; } sort(aa+1,aa+1+n); sum=1; for(j=1;j<=n-1;j++) { if(aa[j]==aa[j+1]) { ans+=sum; sum++; } else { sum=1; } } } cout<<ans<<endl; return 0; }
luogu P10114 [LMXOI Round 1] Size
-
前置知识:若 \(\sum\limits_{i=1}^{n}d_i \le V\) ,则 \(\{ d \}\) 中最多只有 \(\sqrt{V}\) 个不同的数。
-
设 \(sum_{i}\) 表示 \(i\) 在 \(\{ d \}\) 中出现的次数, \(\{ a \}\) 为 \(\{ d \}\) 去重后的序列。\(\sum\limits_{i=1}^{|a|}\sum\limits_{j=1}^{|a|}sum_{a_{i}} \times sum_{a_{j}} \times ((a_{i} \bigoplus a_{j})+(a_{i} \bigotimes a_{j}))\) 即为所求。
点击查看代码
ll d[2000001],a[2000001],sum[50000001]; int main() { ll n,m=0,ans=0,i,j; cin>>n; for(i=1;i<=n;i++) { cin>>d[i]; if(sum[d[i]]==0) { m++; a[m]=d[i]; } sum[d[i]]++; } for(i=1;i<=m;i++) { for(j=1;j<=m;j++) { ans+=sum[a[i]]*sum[a[j]]*(__builtin_popcountll(a[i]+a[j])+__builtin_popcountll(abs(a[i]-a[j]))); } } cout<<ans<<endl; return 0; }
1.29
闲话
- 【数据删除】因昨天晚上的事情被家长接走了,然后机房就变消停了不少。
- 不知道什么时候, \(miaomiao\) 来催了下做题。
- 中午吃完饭后吃了个豆干,然后嗓子就开始疼。
- 下午考勤机(刷脸)到了,不知道是第几次教练给吩咐作息时间。
- 为什么晚上吃饭的时间这么短。
- 晚上吃饭的时候,看了下从机房跑到食堂需要 \(2min\) ,应该可以更快。
- 晚上放的 \(manacher\) 和扩展 \(KMP\) 视频。视频放完后,出去接水的路上,碰见了 \(feifei\) 问两个来上课的 \(HZOI2023\) 都要考期末了为什么还来上课;但平常 \(HZOI2024\) 就算年级部说停课仍来上课,教练问都不问。震惊到了。
- 晚上下课后,跟着 @xrlong 和@wkh2008 在小操场跑了两圈,勉强跟得上。
做题纪要
luogu P4591 [TJOI2018] 碱基序列
-
令 \(f_{i,j}(1 \le i \le k,1 \le j \le |s|)\) 表示使用第 \(i\) 个氨基酸,能匹配到原碱基串的第 \(j\) 个位置的合法方案数。
-
状态转移方程为 \(f_{i,j}=\begin{cases}1 & i=0 \\ \sum\limits_{k=1}^{a_{i}}[s_{j-|ss_{k}|+1} \sim s_{j}=ss_{1} \sim ss_{k_{1 \sim |ss_{k}|}}] \times f_{i-1,j-|ss_{k}|+1-1} & i \ne 0 \end{cases}\) ,为方便代码书写,我们选择枚举左右端点 \(l,r\) 使得 \(l=j-|ss_{k}|+1,r=j\) 。
-
\(\sum\limits_{i=1}^{|s|}f_{k,i}\) 即为所求。
点击查看代码
const ll mod=998244353,base=13331; ll a[20000],b[200][20][20],jc[20000],len[200][20],f[200][20000]; char s1[20000],s2[200][20][20]; void sx_hash(char s[],ll a[],ll len) { for(ll i=0;i<=len;i++) { a[i]=(i==0)?0:((a[i-1]*base%mod+s[i])%mod); } } ll ask_hash(ll a[],ll l,ll r) { return (a[r]-a[l-1]*jc[r-l+1]%mod+mod)%mod; } int main() { ll n,ans=0,maxlen,aa,i,j,l,r,p=1000000007; cin>>n>>(s1+1); maxlen=strlen(s1+1); for(i=0;i<=maxlen;i++) { jc[i]=(i==0)?1:(jc[i-1]*base%mod); } sx_hash(s1,a,maxlen); for(i=0;i<=maxlen;i++) { f[0][i]=1; } for(i=1;i<=n;i++) { cin>>aa; for(j=1;j<=aa;j++) { cin>>(s2[i][j]+1); len[i][j]=strlen(s2[i][j]+1); sx_hash(s2[i][j],b[i][j],len[i][j]); for(l=1,r=l+len[i][j]-1;r<=maxlen;l++,r++) { f[i][r]=(f[i][r]+(ask_hash(a,l,r)==ask_hash(b[i][j],1,len[i][j]))*f[i-1][l-1])%p; } } } for(i=1;i<=maxlen;i++) { ans=(ans+f[n][i])%p; } cout<<ans<<endl; return 0; }
LibreOJ 10150. 「一本通 5.1 练习 1」括号配对
-
令 \(f_{l,r}(1 \le l \le r \le |s|)\) 表示为使从 \(l \sim r\) 变为合法的括号序列至少需要添加的字符数。
-
定义 \(check(l,r)=\begin{cases} true &s[l]='[' \ and \ s[r]=']' \\ true &s[l]='(' \ and \ s[r]=')' \\ false & otherwise \end{cases}\) 。
-
考虑分别对左边或右边进行添加括号以达到合法状态,或者以区间 \(DP\) 的形式拆分成两部分求解,故状态转移方程为 \(f_{l,r}=\begin{cases}1 & l=r \\ \min \{f_{l,r},f_{l+1,r-1},f_{l+1,r}+1,f_{l,r-1}+1,\min\limits_{i=l}^{r-1} \{ f_{l,i}+f_{i+1,r} \} \} & check(l,r)=true \\ \min \{f_{l,r},f_{l+1,r}+1,f_{l,r-1}+1,\min\limits_{i=l}^{r-1} \{ f_{l,i}+f_{i+1,r} \} \} & check(l,r)=false \end{cases}\) 。
-
注意初始值的设定。
点击查看代码
int f[200][200]; char s[200]; int main() { ll n,len,l,r,i; cin>>(s+1); n=strlen(s+1); for(l=1;l<=n;l++) { f[l][l]=1; for(r=l+1;r<=n;r++) { f[l][r]=0x3f3f3f3f; } } for(len=2;len<=n;len++) { for(l=1,r=l+len-1;r<=n;l++,r++) { if((s[l]=='['&&s[r]==']')||(s[l]=='('&&s[r]==')')) { f[l][r]=min(f[l][r],f[l+1][r-1]); } f[l][r]=min(f[l][r],min(f[l+1][r]+1,f[l][r-1]+1)); for(i=l;i<=r-1;i++) { f[l][r]=min(f[l][r],f[l][i]+f[i+1][r]); } } } cout<<f[1][n]<<endl; return 0; }
luogu P4305 [JLOI2011] 不重复数字
-
注意此题较大的输入输出量。
点击查看代码
int main() { int t,n,a,i,j; scanf("%d",&t); for(i=1;i<=t;i++) { scanf("%d",&n); map<int,bool>vis; for(j=1;j<=n;j++) { scanf("%d",&a); if(vis.find(a)==vis.end()) { printf("%d ",a); vis[a]=true; } } printf("\n"); } return 0; }
luogu P3375【模板】KMP
-
\(KMP\) 板子。
点击查看代码
ll nxt[2000000]; char s1[2000000],s2[2000000]; int main() { int n,m,i,j; cin>>(s1+1)>>(s2+1); n=strlen(s1+1); m=strlen(s2+1); for(i=2,nxt[1]=j=0;i<=m;i++) { while(j>=1&&s2[i]!=s2[j+1]) { j=nxt[j]; } j+=(s2[i]==s2[j+1]); nxt[i]=j; } for(i=1,j=0;i<=n;i++) { while(j>=1&&(j==m||s1[i]!=s2[j+1])) { j=nxt[j]; } j+=(s1[i]==s2[j+1]); if(j==m) { cout<<i-m+1<<endl; j=nxt[m]; } } for(i=1;i<=m;i++) { cout<<nxt[i]<<" "; } return 0; }
LibreOJ 10036. 「一本通 2.1 练习 2」Seek the Name, Seek the Fame
-
有一个比较显然的结论: \(Border\) 的 \(Border\) 还是 \(Border\) 。
-
因为 \(next_{i}(1 \le i \le |S|)\) 表示的是 \(S_{1} \sim S_{i}\) 的最长的 \(Border\) 长度,故反着跳 \(next\) 即可。
点击查看代码
ll nxt[400002]; char s[400002]; void print(ll x) { if(x>=1) { print(nxt[x-(nxt[x]==x)]);//若nxt[x]==x成立,则说明从1到x是一个Border cout<<x<<" "; } } int main() { ll n,i,j; while(cin>>(s+1)) { n=strlen(s+1); for(i=2,nxt[1]=j=0;i<=n;i++) { while(j>=1&&s[i]!=s[j+1]) { j=nxt[j]; } j+=(s[i]==s[j+1]); nxt[i]=j; } print(n); cout<<endl; } return 0; }
luogu P4824 [USACO15FEB] Censoring S
-
考虑用一个栈维护剩下的字符串,在匹配成功后将匹配成功的部分弹出栈,再继续进行匹配。故需要额外记录 \(f_{i}(1 \le i \le |S|)\) 表示 \(S\) 中以 \(i\) 结尾的子串与 \(T\) 的前缀能够匹配的最长长度;特别地,当 \(f_{i}=|T|\) 时, \(i\) 为 \(T\) 在 \(S\) 中的某一次出现的位置(左端点)。
-
因为最后需要顺序输出,故使用
deque
代替。点击查看代码
ll nxt[1000002],f[1000002]; char s1[1000002],s2[1000002]; deque<ll>q; int main() { ll n,m,i,j,k; cin>>(s1+1)>>(s2+1); n=strlen(s1+1); m=strlen(s2+1); for(i=2,nxt[1]=j=0;i<=m;i++) { while(j>=1&&s2[i]!=s2[j+1]) { j=nxt[j]; } j+=(s2[i]==s2[j+1]); nxt[i]=j; } for(i=1,j=0;i<=n;i++) { q.push_back(i); while(j>=1&&(j==m||s1[i]!=s2[j+1])) { j=nxt[j]; } j+=(s1[i]==s2[j+1]); f[i]=j; if(j==m) { for(k=1;k<=m;k++) { q.pop_back(); } j=f[(q.empty()==0)?q.back():0];//记得跳回去 } } while(q.empty()==0) { cout<<s1[q.front()]; q.pop_front(); } return 0; }
每日总结、反思
- 以讲题的方式加深对题目的理解,巩固知识点是好的。
- 自己码力不行和思维受限的问题越来越明显了,要想办法解决,一定要想办法解决。
1.30
闲话
- 下午放的 \(LCA\) 视频。
做题纪要
luogu P3435 [POI2006] OKR-Periods of Words
-
简化题意:给定一个字符串 \(S\) ,求 \(S\) 的所有前缀的最长周期的长度之和。
-
前置知识:对于一个字符串 \(S\) ,由于 \(next_{|S|}\) 是 \(S\) 最长 \(Border\) 的长度,故此时有 \(|S|-next_{|S|}\) 为 \(S\) 的最小周期长度(最小循环元长度)。
- 这里可以画图理解一下。
-
本题因要求最大周期,故需要求最小 \(Border\) 长度,需要进行跳 \(next\)。在跳 \(next\) 的过程中,记得记忆化一下。
点击查看代码
ll nxt[1000002],sum[1000002]; char s[1000002]; int main() { ll n,i,j,ans=0; cin>>n>>(s+1); for(i=2,nxt[1]=j=0;i<=n;i++) { while(j>=1&&s[i]!=s[j+1]) { j=nxt[j]; } j+=(s[i]==s[j+1]); nxt[i]=j; sum[i]=(j>=1&&nxt[j]==0)?j:sum[nxt[i]]; ans+=(sum[i]==0)?0:i-sum[i]; } cout<<ans<<endl; return 0; }
UVA10298 Power Strings
-
前置知识:当 \((|S|-next_{|S|})||S|\) 时,此时有 \(\dfrac{|S|}{|S|-next_{|S|}}\) 为 \(S\) 的最大循环次数;否则, \(S\) 的最大循环次数为 \(1\) 。
点击查看代码
ll nxt[1000002],sum[1000002]; char s[1000002]; int main() { ll n,i,j; while(cin>>(s+1)) { n=strlen(s+1); if(n==1&&s[1]=='.') { break; } else { for(i=2,nxt[1]=j=0;i<=n;i++) { while(j>=1&&s[i]!=s[j+1]) { j=nxt[j]; } j+=(s[i]==s[j+1]); nxt[i]=j; } cout<<((n%(n-nxt[n])==0)?(n/(n-nxt[n])):1)<<endl; } } return 0; }
luogu P4391 [BOI2009]Radio Transmission 无线传输
-
当 \((|S|-next_{|S|}) \nmid |S|\) 时,将 \(|S|-next_{|S|}\) 复制 \(2\) 次即可;当 \((|S|-next_{|S|})||S|\) 时,将 \(|S|-next_{|S|}\) 复制 \(\dfrac{|S|}{|S|-next_{|S|}}\) 次即可。故 \(|S|-next_{|S|}\) 即为所求。
点击查看代码
ll nxt[1000002]; char s[1000002]; int main() { ll n,i,j; cin>>n>>(s+1); for(i=2,nxt[1]=j=0;i<=n;i++) { while(j>=1&&s[i]!=s[j+1]) { j=nxt[j]; } j+=(s[i]==s[j+1]); nxt[i]=j; } cout<<n-nxt[n]<<endl; return 0; }
luogu P2375 [NOI2014] 动物园
-
令 \(cnt_{i}(1 \le i \le |S|)\) 表示是 \(S_{1} \sim S_{i}\) 的 \(Border\) 数量,故有 \(cnt_{i}=cnt_{next_{i}}+1\) 。
- 这里可以画图理解一下。
-
在查询 \(sum_{i}\) 时,将 \(next\) 往回跳,直至 \(2j \le i\) 从而满足不重叠条件,此时有 \(num_{i}=cnt_{j}\)。
点击查看代码
ll nxt[1000002],num[1000002],cnt[1000002]; char s[1000002]; int main() { ll t,n,i,j,k,ans,p=1000000007; cin>>t; for(k=1;k<=t;k++) { cin>>(s+1); n=strlen(s+1); ans=cnt[1]=1; for(i=2,nxt[1]=j=0;i<=n;i++) { while(j>=1&&s[i]!=s[j+1]) { j=nxt[j]; } j+=(s[i]==s[j+1]); nxt[i]=j; cnt[i]=cnt[nxt[i]]+1; } for(i=2,j=0;i<=n;i++) { while(j>=1&&s[i]!=s[j+1]) { j=nxt[j]; } j+=(s[i]==s[j+1]); while(j*2>i) { j=nxt[j]; } num[i]=cnt[j]; ans=ans*(num[i]+1)%p; } cout<<ans<<endl; } return 0; }
luogu P5829【模板】失配树
-
失配树板子。
-
在 \(KMP\) 的过程中,连一条从 \(next_{i}\) 到 \(i\) 的有向边,最后就得到了一棵以 \(0\) 为根节点的树。因 \(Border\) 必须是真前缀,故最长公共前缀即失配树上的除自己之外的最近公共祖先。故当 \(LCA(p,q) \in \{ p,q\}\) 时, \(fa_{LCA(p,q)}\) 即为所求。
- 理解:若 \(A\) 是 \(B\) 的 \(Border\) , \(B\) 是 \(C\) 的 \(Border\) ,则 \(A\) 是 \(C\) 的 \(Border\) 。
点击查看代码
struct node { ll nxt,to,w; }e[1000002]; ll nxt[1000002],head[1000002],fa[1000002][25],dep[1000002],N,cnt=0; char s[1000002]; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(ll x,ll father) { fa[x][0]=father; dep[x]=dep[father]+1; for(ll i=1;(1<<i)<=dep[x];i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs(e[i].to,x); } } } ll fail_lca(ll x,ll y) { if(dep[x]>dep[y]) { swap(x,y); } for(ll i=N;i>=0;i--) { if(dep[x]+(1<<i)<=dep[y]) { y=fa[y][i]; } } if(x!=y) { for(ll i=N;i>=0;i--) { if(fa[x][i]!=fa[y][i]) { x=fa[x][i]; y=fa[y][i]; } } } return fa[x][0];//注意这里和普通LCA不同 } int main() { ll n,m,l,r,i,j; cin>>(s+1)>>m; n=strlen(s+1); N=log2(n+1)+1; for(i=2,nxt[1]=j=0;i<=n;i++) { while(j>=1&&s[i]!=s[j+1]) { j=nxt[j]; } j+=(s[i]==s[j+1]); nxt[i]=j; } for(i=1;i<=n;i++) { add(nxt[i],i); } dfs(0,n+1); for(i=1;i<=m;i++) { cin>>l>>r; cout<<fail_lca(l,r)<<endl; } return 0; }
BZOJ1461 字符串的匹配 | BZOJ1892 Match
-
类似 luogu P1972 [SDOI2009]HH的项链 和树状数组求 luogu P1908 逆序对 的过程,同样使用权值树状数组维护排名。在匹配失败或已经有 \(A\) 的子串和 \(B\) 相等时,均减去对序列的影响。
-
注意到可能会有相等的元素,故我们需要同时判断小于等于该数和小于该数是否满足题意。
-
因在本题中判断两个字符串相等的条件是元素排名相等,故 \(KMP\) 的相等判定也需稍作修改。
-
@Pursuing_OIer 提供了一组 \(hack\) 数据。
点击查看数据
input: 4 3 5 5 5 5 5 5 5 5 ans: 2 1 2
点击查看代码
ll nxt[500002],c[500002],rankd[500002],rankx[500002],ans[500002],s1[500002],s2[500002]; ll lowbit(ll x) { return (x&(-x)); } void add(ll n,ll x,ll val) { for(ll i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } ll getsum(ll x) { ll ans=0; for(ll i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } int main() { ll n,m,s,i,j,k,sum=0; cin>>n>>m>>s; for(i=1;i<=n;i++) { cin>>s1[i]; } for(i=1;i<=m;i++) { cin>>s2[i]; add(s,s2[i],1); rankd[i]=getsum(s2[i]); rankx[i]=getsum(s2[i]-1); } memset(c,0,sizeof(c)); for(i=2,nxt[1]=j=0;i<=m;i++) { add(s,s2[i],1); while(j>=1&&(getsum(s2[i])!=rankd[j+1]||getsum(s2[i]-1)!=rankx[j+1])) { for(k=i-j;k<=i-nxt[j]-1;k++)//这里可以画图理解一下 { add(s,s2[k],-1);//减去影响 } j=nxt[j]; } j+=(getsum(s2[i])==rankd[j+1]&&getsum(s2[i]-1)==rankx[j+1]); nxt[i]=j; } memset(c,0,sizeof(c)); for(i=1,j=0;i<=n;i++) { add(s,s1[i],1); while(j>=1&&(getsum(s1[i])!=rankd[j+1]||getsum(s1[i]-1)!=rankx[j+1])) { for(k=i-j;k<=i-nxt[j]-1;k++)//这里可以画图理解一下 { add(s,s1[k],-1);//减去影响 } j=nxt[j]; } j+=(getsum(s1[i])==rankd[j+1]&&getsum(s1[i]-1)==rankx[j+1]); if(j==m) { for(k=i-j+1;k<=i-nxt[j];k++)//这里可以画图理解一下 { add(s,s1[k],-1);//减去影响 } sum++; ans[sum]=i-j+1; j=nxt[m]; } } cout<<sum<<endl; for(i=1;i<=sum;i++) { cout<<ans[i]<<endl; } return 0; }
luogu P3167 [CQOI2014] 通配符匹配
- 多倍经验: luogu P2536 [AHOI2005]病毒检测
- 非正解
-
因通配符个数不超过 \(10\) ,考虑爆搜。
- 因搜索顺序问题,如果正着搜会得到 \(40pts\) ,但因数据没满,故反着搜能够通过。
- \(hack\) 数据生成器详见本博客 \(5\) 楼。
- 因搜索顺序问题,如果正着搜会得到 \(40pts\) ,但因数据没满,故反着搜能够通过。
-
记通配符个数为 \(m\) ,最坏情况下时间复杂度目测为 \(O(n|s|^{m+1})\) 。
点击查看代码
char s1[200000],s2[200000]; bool dfs(ll sx,ll sy) { if(sx==0) { return (sy==0); } else { if(sy==0) { for(ll i=sx;i>=1;i--) { if(s1[i]!='*') { return false; } } return true; } else { if(s1[sx]=='*') { for(ll i=sy;i>=0;i--) { if(dfs(sx-1,i)==true) { return true; } } return false; } else { return ((s1[sx]=='?'||s1[sx]==s2[sy])?dfs(sx-1,sy-1):false); } } } } int main() { ll n,lens,i; cin>>(s1+1)>>n; lens=strlen(s1+1); for(i=1;i<=n;i++) { cin>>(s2+1); if(dfs(lens,strlen(s2+1))==true) { cout<<"YES"<<endl; } else { cout<<"NO"<<endl; } } return 0; }
-
- 正解
-
为方便代码书写,在模式串和文本串最后均添加一个
?
。 -
将模式串按照通配符划分成若干段。设 \(f_{i,j}\) 表示由模式串分出的第 \(i\) 个子串是否能够匹配到文本串第 \(j\) 位。类比爆搜做法,分别对通配符进行分讨,然后进行转移。
点击查看代码
const ll mod=998244354,base=13331; ll a[1010],b[1010],jc[1010],vis[1010],f[1010][1010]; char s1[1010],s2[1010]; void sx_hash(char s[],ll a[],ll len) { for(ll i=0;i<=len;i++) { a[i]=(i==0)?0:((a[i-1]*base%mod+s[i])%mod); } } ll ask_hash(ll a[],ll l,ll r) { return (a[r]-a[l-1]*jc[r-l+1]%mod+mod)%mod; } int main() { ll n,m,t,i,j,k,l,r,len,cnt=0,ans=0; cin>>(s1+1)>>t; n=strlen(s1+1)+1; s1[n]='?'; sx_hash(s1,a,n); for(i=1;i<=n;i++) { if(s1[i]=='*'||s1[i]=='?') { cnt++; vis[cnt]=i; } } for(i=0;i<=500;i++) { jc[i]=(i==0)?1:(jc[i-1]*base%mod); } for(i=1;i<=t;i++) { cin>>(s2+1); memset(f,0,sizeof(f)); f[0][0]=1; m=strlen(s2+1)+1; s2[m]='?'; sx_hash(s2,b,m); for(j=0;j<=cnt;j++) { if(j!=0&&s1[vis[j]]=='*') { for(k=1;k<=m;k++) { f[j][k]=(f[j][k-1]==1)?1:f[j][k]; } } if(j+1<=cnt) { len=(vis[j+1]-1)-(vis[j]+1)+1; for(l=1,r=l+len-1;r<=m;l++,r++) { if(f[j][l-1]==1) { if(ask_hash(a,vis[j]+1,vis[j+1]-1)==ask_hash(b,l,r)) { if(s1[vis[j+1]]=='?') { f[j+1][r+1]=1; } else { f[j+1][r]=1; } } } } } } ans+=(f[cnt][m]==0); } cout<<ans<<endl; return 0; }
-
每日总结、反思
- 合理地利用画图是好的。
- 要学会正确地分析时间复杂度。
1.31
闲话
- \(miaomiao\) 上午 \(7:50 \sim 11:20\) 安排了一场模拟赛。下午让 @pbbqqd 讲了 \(T3\) , @wkh2008 讲了 \(T4\) 。
- 问了下 @wkh2008 和 @Pursuing_OIer ,发现他们也嗓子疼,看了眼百度热搜发现这次咽喉炎可能又是传染病。
做题纪要
HZOJ 520. 走迷宫
- 详见 2024初三年前集训测试1 T3 走迷宫 。
luogu P4552 [Poetize6] IncDec Sequence
-
第一问详见 2024初三年前集训测试1 T4 鸭子游戏 。
-
第二问在第一问的基础上,令 \(b_{n+1}=-a_{n}\) 。在原来第二步的基础上, \(\max(p,q)-\min(p,q)\) 被分配给了 \(b_{1}\) 和 \(b_{n+1}\) 进行操作。设 \(x\) 表示此时 \(b_{1}\) 进行操作的次数, \(y\) 表示此时 \(b_{n+1}\) 进行操作的次数,故有 \(x+y=\max(p,q)-\min(p,q)\) ,其中 \(x,y \in \mathbb{N}^{*}\) 。易知 \(x\) 共有 \(\max(p,q)-\min(p,q)+1\) 种不同的取值,即最终可得到 \(\max(p,q)-\min(p,q)+1\) 种不同的数列。
点击查看代码
ll a[100002],b[100002]; int main() { ll n,p=0,q=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; b[i]=a[i]-a[i-1]; } for(i=2;i<=n;i++) { p+=(b[i]>0)?b[i]:0; q+=(b[i]<0)?-b[i]:0; } cout<<max(p,q)<<endl; cout<<max(p,q)-min(p,q)+1<<endl; return 0; }
luogu P3823 [NOI2017]蚯蚓排队
-
观察到 \(k \le 50\) ,每次修改最多只会对前后 \(50\) 个位置内的字符串产生影响,故每次连接、分开时使用双向链表进行模拟,进行暴力更改。
-
因要统计每个字符串出现的次数,故将字符串进行 \(hash\) 后插入哈希表中进行统计。
-
哈希表的实现因
map
和unordered_map
常数较大,故选择手写哈希表或使用黑科技平板电视pb_ds
中的gp_hash_table
。- 在本题中
gp_hash_table
比cc_hash_table
跑得更快一点。
- 在本题中
-
本题略带卡常,故使用
unsigned long long
的自然溢出来实现。点击查看代码
#include<ext/pb_ds/tree_policy.hpp> #include<ext/pb_ds/assoc_container.hpp> using namespace __gnu_pbds; const ll mod=998244353,base=13331; ull a[200002],b[10000002],jc[10000002]; int pre[200002],nxt[200002]; char s1[200002][3],ls[100],s2[10000002]; gp_hash_table<ull,ll>vis; static inline void sx_hash(char s1[],ull a[],int len) { for(int i=0;i<=len;++i) { a[i]=(i==0)?0:(a[i-1]*base+s1[i]); } } static inline ull ask_hash(ull a[],int l,int r) { return (a[r]-a[l-1]*jc[r-l+1]); } int main() { ll ans; int n,m,pd,u,v,l,r,len,i,j; scanf("%d%d",&n,&m); for(i=0;i<=10000000;++i) { jc[i]=(i==0)?1:(jc[i-1]*base); } for(i=1;i<=n;++i) { scanf("%s",s1[i]+1); len=strlen(s1[i]+1); sx_hash(s1[i],a,len); ++vis[ask_hash(a,1,len)]; } for(i=1;i<=m;++i) { scanf("%d",&pd); if(pd==1) { scanf("%d%d",&u,&v); nxt[u]=v; pre[v]=u; l=49; r=50; memset(ls,0,sizeof(ls)); for(j=u;j!=0&&l>=1;j=pre[j],--l) { ls[l]=s1[j][1]; } ++l; for(j=v;j!=0&&r<=98;j=nxt[j],++r) { ls[r]=s1[j][1]; } --r; sx_hash(ls,b,r); for(j=l;j<=49;++j) { for(len=51-j;len<=min(50,r-j+1);++len) { ++vis[ask_hash(b,j,j+len-1)]; } } } if(pd==2) { scanf("%d",&u); v=nxt[u]; nxt[u]=pre[v]=0; l=49; r=50; memset(ls,0,sizeof(ls)); for(j=u;j!=0&&l>=1;j=pre[j],--l) { ls[l]=s1[j][1]; } ++l; for(j=v;j!=0&&r<=98;j=nxt[j],++r) { ls[r]=s1[j][1]; } --r; sx_hash(ls,b,r); for(j=l;j<=49;++j) { for(len=51-j;len<=min(50,r-j+1);++len) { --vis[ask_hash(b,j,j+len-1)]; } } } if(pd==3) { scanf("%s%d",s2+1,&v); len=strlen(s2+1); sx_hash(s2,b,len); ans=1; for(j=v;j<=len;++j) { ans=ans*vis[ask_hash(b,j-v+1,j)]%mod; } printf("%lld\n",ans); } } return 0; }
每日总结、反思
- 常见、典型的技巧要记住,因为不知道什么时候就用到了。
2.1
闲话
- 上午放的 \(\varphi,quick \ pow,exgcd,CRT,exCRT\) 视频。
- \(feifei\) 称鉴于初中生到位情况良好,所以决定撤去考勤机,让我们以后自觉到位。然后考勤机就出现在了隔壁高一的机房,乐。
做题纪要
luogu P8306 【模板】字典树
-
\(Trie\) 树板子。
-
本题选择记录 \(sum_{i}\) 表示从根节点开始到节点 \(i\) 有多少个模板串以此作为前缀。在搜索 \(T\) 时,搜索完成后直接返回 \(sum\) 即可。
-
每组数据前记得手动清空数组。
点击查看代码
int trie[3000010][100],sum[3000010],tot=0; char s[3000010]; int val(char x) { return x-'0'; } void insert(char s[],int len) { int x=0,i; for(i=0;i<=len-1;i++) { if(trie[x][val(s[i])]==0) { tot++; trie[x][val(s[i])]=tot; } x=trie[x][val(s[i])]; sum[x]++; } } int find(char s[],int len) { int x=0,i; for(i=0;i<=len-1;i++) { if(trie[x][val(s[i])]==0) { return 0; } x=trie[x][val(s[i])]; } return sum[x]; } int main() { int t,n,q,i,j,k; cin>>t; for(i=1;i<=t;i++) { cin>>n>>q; for(j=0;j<=tot;j++) { sum[j]=0; for(k=0;k<=99;k++) { trie[j][k]=0; } } tot=0; for(j=1;j<=n;j++) { cin>>s; insert(s,strlen(s)); } for(j=1;j<=q;j++) { cin>>s; cout<<find(s,strlen(s))<<endl; } } return 0; }
AcWing 142. 前缀统计
-
\(Trie\) 树板子。
-
本题选择记录 \(sum_{i}\) 表示节点 \(i\) 是多少个字符串的末尾节点。在搜索 \(T\) 时,沿途累加每个节点的 \(sum\) 即可。
点击查看代码
int trie[1000000][30],sum[1000000],tot=0; char s[1000000]; int val(char x) { return x-'a'+1; } void insert(char s[],int len) { int x=0,i; for(i=1;i<=len;i++) { if(trie[x][val(s[i])]==0) { tot++; trie[x][val(s[i])]=tot; } x=trie[x][val(s[i])]; } sum[x]++; } int find(char s[],int len) { int x=0,ans=0,i; for(i=1;i<=len;i++) { if(trie[x][val(s[i])]==0) { break; } x=trie[x][val(s[i])]; ans+=sum[x]; } return ans; } int main() { int n,q,i; cin>>n>>q; for(i=1;i<=n;i++) { cin>>(s+1); insert(s,strlen(s+1)); } for(i=1;i<=q;i++) { cin>>(s+1); cout<<find(s,strlen(s+1))<<endl; } return 0; }
SP4033 PHONELST - Phone List
-
多倍经验: UVA11362 Phone List
-
如果在一开始将所有字符串全都插进一棵 \(Trie\) 树里,最后再寻找要特判找到自己的时候,或者将是否有前缀转化出现次数是否大于 \(1\) 。
点击查看代码
ll trie[120000][15],sum[120000],tot=0; char s[120000][15]; int val(char x) { return x-'0'; } void insert(char s[],ll len) { ll x=0,i; for(i=1;i<=len;i++) { if(trie[x][val(s[i])]==0) { tot++; trie[x][val(s[i])]=tot; } x=trie[x][val(s[i])]; sum[x]++; } } ll find(char s[],ll len) { ll x=0,i; for(i=1;i<=len;i++) { if(trie[x][val(s[i])]==0) { return 0; } x=trie[x][val(s[i])]; } return sum[x]; } int main() { ll t,n,flag,i,j; cin>>t; for(i=1;i<=t;i++) { cin>>n; tot=flag=0; memset(trie,0,sizeof(trie)); memset(sum,0,sizeof(sum)); for(j=1;j<=n;j++) { cin>>(s[j]+1); insert(s[j],strlen(s[j]+1)); } for(j=1;j<=n;j++) { if(find(s[j],strlen(s[j]+1))>1) { flag=1; break; } } if(flag==1) { cout<<"NO"<<endl; } else { cout<<"YES"<<endl; } } return 0; }
luogu P2580 于是他错误的点名开始了
-
注意要判断报的名字在名单上是否出现。
点击查看代码
int trie[600000][30],sum[600000],num[600000],tot; char s[100]; int val(char x) { return x-'a'+1; } void insert(char s[],int len) { int x=0,i; for(i=1;i<=len;i++) { if(trie[x][val(s[i])]==0) { tot++; trie[x][val(s[i])]=tot; } x=trie[x][val(s[i])]; } sum[x]++; } int find(char s[],int len) { int x=0,i; for(i=1;i<=len;i++) { if(trie[x][val(s[i])]==0) { return -1; } x=trie[x][val(s[i])]; } if(sum[x]==0) { return -1; } else { num[x]++; return num[x]; } } int main() { int n,m,ans,i; cin>>n; for(i=1;i<=n;i++) { cin>>(s+1); insert(s,strlen(s+1)); } cin>>m; for(i=1;i<=m;i++) { cin>>(s+1); ans=find(s,strlen(s+1)); if(ans==-1) { cout<<"WRONG"<<endl; } if(ans==1) { cout<<"OK"<<endl; } if(ans>1) { cout<<"REPEAT"<<endl; } } return 0; }
LibreOJ 10050. 「一本通 2.3 例 2」The XOR Largest Pair
-
\(01Trie\) 树板子。把 \(a_{i}(1 \le i \le n)\) 视作一个 \(32\) 位的二进制 \(01\) 串,然后插进一棵 \(Trie\) 树里。
-
异或性质详见 【学习笔记】数学知识-高斯消元与线性空间 。
-
从贪心角度的分析,我们要尽可能走与当前位相反的字符。
点击查看代码
ll trie[3300000][3],a[100001],tot=0; void insert(ll s) { ll x=0,i; for(i=31;i>=0;i--) { if(trie[x][(s>>i)&1]==0) { tot++; trie[x][(s>>i)&1]=tot; } x=trie[x][(s>>i)&1]; } } ll find(ll s) { ll x=0,ans=0,i; for(i=31;i>=0;i--) { if(trie[x][((s>>i)&1)^1]==0) { x=trie[x][(s>>i)&1]; } else { x=trie[x][((s>>i)&1)^1]; ans=ans^(1<<i);//这里等价于ans=ans|(1<<i); } } return ans; } int main() { ll n,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; insert(a[i]); } for(i=1;i<=n;i++) { ans=max(ans,find(a[i])); } cout<<ans<<endl; return 0; }
CF706D Vasiliy's Multiset
-
\(01Trie\) 带删除板子。
点击查看代码
int trie[10000000][3],sum[10000000],tot=0; void insert(int s) { int x=0,i; for(i=30;i>=0;i--) { if(trie[x][(s>>i)&1]==0) { tot++; trie[x][(s>>i)&1]=tot; } x=trie[x][(s>>i)&1]; sum[x]++; } } void earse(int s) { int x=0,i; for(i=30;i>=0;i--) { x=trie[x][(s>>i)&1]; sum[x]--; } } int find(int s) { int x=0,ans=0,i; for(i=30;i>=0;i--) { if(trie[x][((s>>i)&1)^1]==0||sum[trie[x][((s>>i)&1)^1]]==0) { x=trie[x][(s>>i)&1]; } else { x=trie[x][((s>>i)&1)^1]; ans=ans^(1<<i); } } return ans; } int main() { int n,x,i; char pd; cin>>n; insert(0); for(i=1;i<=n;i++) { cin>>pd>>x; if(pd=='+') { insert(x); } if(pd=='-') { earse(x); } if(pd=='?') { cout<<find(x)<<endl; } } return 0; }
luogu P4551 最长异或路径
-
luogu P2420 让我们异或吧的结论:设 \(A_{i}\) 表示从根节点到 \(i\) 的路径上的所有边权的异或值。即 \(A_{i}=A_{fa_{i}} \bigoplus w_{fa_{i}->i}\)。由异或性质,因两点的 \(LCA\) 到根的路径异或和会被计算两次从而对答案不产生影响,故 \(A_{u} \bigoplus A_{v}\) 即为所求。
点击查看代码
struct node { ll nxt,to,w; }e[200001]; ll trie[3300000][3],head[200001],dep[200001],a[200001],tot=0,cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs(ll x,ll fa,ll w) { a[x]=a[fa]^w; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x,e[i].w); } } } void insert(ll s) { ll x=0,i; for(i=31;i>=0;i--) { if(trie[x][(s>>i)&1]==0) { tot++; trie[x][(s>>i)&1]=tot; } x=trie[x][(s>>i)&1]; } } ll find(ll s) { ll x=0,ans=0,i; for(i=31;i>=0;i--) { if(trie[x][((s>>i)&1)^1]==0) { x=trie[x][(s>>i)&1]; } else { x=trie[x][((s>>i)&1)^1]; ans=ans^(1<<i); } } return ans; } int main() { ll n,u,v,w,i,ans=0; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } dfs(1,0,0); for(i=1;i<=n;i++) { insert(a[i]); } for(i=1;i<=n;i++) { ans=max(ans,find(a[i])); } cout<<ans<<endl; return 0; }
LibreOJ 10051. 「一本通 2.3 例 3」Nikitosh 和异或
-
考虑把原串分成 \([1,i]\) 和 \([i+1,n]\) 两段,分别进行统计求和。
点击查看代码
int trie[13000000][3],a[13000000],sum1[13000000],sum2[13000000],tot=0; void insert(int s) { int x=0,i; for(i=30;i>=0;i--) { if(trie[x][(s>>i)&1]==0) { tot++; trie[x][(s>>i)&1]=tot; } x=trie[x][(s>>i)&1]; } } int find(int s) { int x=0,ans=0,i; for(i=30;i>=0;i--) { if(trie[x][((s>>i)&1)^1]==0) { x=trie[x][(s>>i)&1]; } else { x=trie[x][((s>>i)&1)^1]; ans=ans^(1<<i); } } return ans; } int main() { int n,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=n;i++) { insert(a[i]); sum1[i]=max(sum1[i-1],find(a[i])); } memset(trie,0,sizeof(trie)); for(i=n;i>=1;i--) { insert(a[i]); sum2[i]=max(sum2[i+1],find(a[i])); } for(i=1;i<=n-1;i++) { ans=max(ans,sum1[i]+sum2[i-1]); } cout<<ans<<endl; return 0; }
luogu P3805 【模板】manacher 算法
-
马拉车板子。
点击查看代码
int r[34000000]; string s,t=" #"; int main() { int n,maxr=0,id=0,ans=0,i; cin>>s; for(i=0;i<s.size();i++) { t+=s[i]; t+="#"; } n=t.size()-1; for(i=1;i<=n;i++) { r[i]=(i<maxr)?min(r[id-(i-id)],maxr-i):1;//一开始自己能与自己匹配 while(1<=i-r[i]&&i+r[i]<=n&&t[i+r[i]]==t[i-r[i]]) { r[i]++; } if(maxr<i+r[i]) { maxr=i+r[i]; id=i; } } for(i=1;i<=n;i++) { ans=max(ans,r[i]-1); } cout<<ans<<endl; return 0; }
luogu P3501 [POI2010]ANT-Antisymmetry
-
多倍经验: SP15569 Antisymmetry
-
类似马拉车,同样记录最右边的“反对称”字符串右端点。
-
因最终要除去
#
对答案的影响,故 \(\sum\limits_{i=1}^{|T|}\dfrac{r_{i}}{2}\) 即为所求。点击查看代码
ll r[1600000]; string s,t=" #"; int main() { ll m,n,maxr=0,id=0,ans=0,i; cin>>m>>s; for(i=0;i<s.size();i++) { t+=s[i]; t+="#"; } n=t.size()-1; for(i=1;i<=n;i++) { r[i]=(i<maxr)?min(r[id-(i-id)],maxr-i):0;//因为一开始可能自己不一定能与自己匹配 while(1<=i-r[i]&&i+r[i]<=n&&((t[i+r[i]]=='#'&&t[i+r[i]]==t[i-r[i]])||(t[i+r[i]]!='#'&&t[i+r[i]]+t[i-r[i]]=='0'+'1'))) { r[i]++; } if(maxr<i+r[i]) { maxr=i+r[i]; id=i; } } for(i=1;i<=n;i++) { ans+=r[i]/2; } cout<<ans<<endl; return 0; }
BZOJ3790 神奇项链
-
发现一个回文串可以控制从左端点到右端点的部分,可以理解为区间上的一条线。然后就转化为可重叠区间线段覆盖问题了,贪心或树状数组优化 \(DP\) 维护即可。
- 贪心策略:以左端点为第一关键字从小到大排序,以右端点为第二关键字从大到小排序。每次选择一个左端点在当前范围内,并且右端点最靠右的线段。
点击查看代码
struct node { int l,r; }a[4000000]; int r[4000000]; string s,t; bool cmp(node a,node b) { return (a.l==b.l)?(a.r>b.r):(a.l<b.l); } int main() { int n,maxr,id,maxx,ans,i,j; while(cin>>s) { maxr=id=ans=0; t=" #"; for(i=0;i<s.size();i++) { t+=s[i]; t+='#'; } n=t.size()-1; for(i=1;i<=n;i++) { r[i]=(i<maxr)?min(r[id-(i-id)],maxr-i):1; while(1<=i-r[i]&&i+r[i]<=n&&t[i+r[i]]==t[i-r[i]]) { r[i]++; } if(maxr<i+r[i]) { maxr=i+r[i]; id=i; } } for(i=1;i<=n;i++) { a[i].l=i-r[i]+1; a[i].r=i+r[i]-1; } sort(a+1,a+1+n,cmp); i=a[1].r; j=2; while(i<=n-1) { maxx=i; while(a[j].l<=i&&j<=n) { maxx=max(maxx,a[j].r); j++; } i=maxx; ans++; } cout<<ans<<endl; } return 0; }
每日总结、反思
- 提升做题效率,不要轻易被外界事物吸引、打扰。
2.2
闲话
- \(miaomiao\) 上午 \(7:40 \sim 12:10\) 安排了一场模拟赛。
- 题解: 2024初三年前集训测试2
- 感觉 \(VScode\) 的终端在开了自动保存后貌似有延迟。
- 其实是 \(VScode\) 的自动保存延迟,改了之后就好多了。
- \(miaomiao\) 终于贯彻了 \(bobo\) 的作风,明确表示假期集训期间禁止穿校服。
- 高一、高二的放假了,机房里有些人跟着一起走了。
做题纪要
luogu P1368 【模板】最小表示法
-
最小表示法板子。
点击查看代码
int s[700000]; int main() { int n,i,l,r,len; cin>>n; for(i=1;i<=n;i++) { cin>>s[i]; s[n+i]=s[i]; } l=1; r=2; while(l<=n&&r<=n) { len=0; while(len<=n-1&&s[l+len]==s[r+len]) { len++; } if(len==n) { break; } else { if(s[l+len]>s[r+len]) { l+=len+1;//因为此时l+1,l+2...l+k一定不是最优解 l+=(l==r); } else { r+=len+1; r+=(l==r); } } } for(i=min(l,r);i<=n;i++) { cout<<s[i]<<" "; } for(i=1;i<=min(l,r)-1;i++) { cout<<s[i]<<" "; } return 0; }
luogu P2397 yyy loves Maths VI (mode)
-
摩尔投票法板子。
- 摩尔投票法只能找到在序列内出现次数严格大于 \(\left\lceil \dfrac{n}{2} \right\rceil\) 的众数。
- 在不保证存在众数的情况下,在摩尔投票法结束后,需要判断下得到的数是否为众数。
-
算法的核心思想是用不同候选人之间的票数相互抵消,并记录抵消完此时候选人是否仍然合法,若不合法,则更改下一个。
点击查看代码
int main() { ll n,a,i,num=0,sum=1; cin>>n; for(i=1;i<=n;i++) { cin>>a; if(a==num) { sum++; } else { sum--; } if(sum==0) { num=a; sum++; } } cout<<num<<endl; return 0; }
luogu P4555 [国家集训队]最长双回文串
-
类似 LibreOJ 10051. 「一本通 2.3 例 3」Nikitosh 和异或 ,将原字符串分成两部分,分别进行统计求和。
-
设 \(left_{i}\) 表示以 \(i\) 为左端点的最长回文串长度, \(right_{i}\) 表示以 \(i\) 为右端点的最长回文串长度。
-
在马拉车的过程中,找到回文串长度后,对该回文串内进行暴力转移,时间复杂度为 \(O(|T|^2)\) 。
-
考虑在马拉车的过程中只对找到的最长回文串的左右端点进行转移。然后限制左右端点只能为
#
,得到状态转移方程 \(left_{i}=\max(left_{i},left_{i-2}+2),right_{i}=\max(right_{i},right_{i-2}+2)\) 。 -
因我们实际的操作是以
#
为左右端点的,故 \(\max\limits_{i=1}^{|T|} \{ [left_{i} \ne 0] \times [right_{i} \ne 0] \times (left_{i}+right_{i}) \}\) 即为所求。点击查看代码
ll r[400000],sum1[400000],sum2[400000]; string s,t=" #"; int main() { ll n,maxr=0,id=0,ans=0,i; cin>>s; for(i=0;i<s.size();i++) { t+=s[i]; t+='#'; } n=t.size()-1; for(i=1;i<=n;i++) { r[i]=(i<maxr)?min(r[id-(i-id)],maxr-i):0; while(1<=i-r[i]&&i+r[i]<=n&&t[i+r[i]]==t[i-r[i]]) { r[i]++; } if(maxr<i+r[i]) { maxr=i+r[i]; id=i; } sum1[i+r[i]-1]=max(sum1[i+r[i]-1],r[i]-1); sum2[i-r[i]+1]=max(sum2[i-r[i]+1],r[i]-1); } for(i=n;i>=3;i-=2) { sum1[i]=max(sum1[i],sum1[i+2]-2); } for(i=3;i<=n;i+=2) { sum2[i]=max(sum2[i],sum2[i-2]-2); } for(i=1;i<=n;i++) { ans=(sum1[i]!=0&&sum2[i]!=0)?max(ans,sum1[i]+sum2[i]):ans; } cout<<ans<<endl; return 0; }
luogu P3846 [TJOI2007] 可爱的质数/【模板】BSGS
-
\(BSGS\) 板子。
点击查看代码
ll qpow(ll a,ll b,ll p) { ll ans=1; while(b>0) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } ll bsgs(ll a,ll b,ll p) { if(1%p==b%p) { return 0; } else { map<ll,ll>vis; ll k=sqrt(p)+1,i,sum; for(i=0;i<=k-1;i++) { vis[b*qpow(a,i,p)%p]=i; } a=qpow(a,k,p); for(i=0;i<=k;i++) { sum=qpow(a,i,p); if(vis.find(sum)!=vis.end()) { if(i*k-vis[sum]>=0) { return i*k-vis[sum]; } } } return -1; } } int main() { ll a,b,p,ans; cin>>p>>a>>b; ans=bsgs(a,b,p); if(ans==-1) { cout<<"no solution"<<endl; } else { cout<<ans<<endl; } return 0; }
luogu P2485 [SDOI2011]计算器
-
同余半家桶。
-
不保证 \(\gcd(a,p)=1\) ,记得特判。
点击查看代码
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; } ll qpow(ll a,ll b,ll p) { ll ans=1; while(b>0) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } ll bsgs(ll a,ll b,ll p) { if(1%p==b%p) { return 0; } else { map<ll,ll>vis; ll k=sqrt(p)+1,i,sum; for(i=0;i<=k-1;i++) { vis[b*qpow(a,i,p)%p]=i; } a=qpow(a,k,p); for(i=0;i<=k;i++) { sum=qpow(a,i,p); if(vis.find(sum)!=vis.end()) { if(i*k-vis[sum]>=0) { return i*k-vis[sum]; } } } return -1; } } int main() { ll t,k,a,b,p,ans,i; cin>>t>>k; for(i=1;i<=t;i++) { cin>>a>>b>>p; if(k==1) { cout<<qpow(a,b,p)<<endl; } if(k==2) { if(b%gcd(a,p)==0) { cout<<qpow(a,p-2,p)*b%p<<endl; } else { cout<<"Orz, I cannot find x!"<<endl; } } if(k==3) { if(a%p==0) { if(b%p==0) { cout<<1<<endl; } else { cout<<"Orz, I cannot find x!"<<endl; } } else { ans=bsgs(a,b,p); if(ans==-1) { cout<<"Orz, I cannot find x!"<<endl; } else { cout<<ans<<endl; } } } } return 0; }
HZOJ 860. 上海
- 详见 2024初三年前集训测试2 T1 上海 。
HZOJ 861. 华二
- 详见 2024初三年前集训测试2 T2 华二 。
每日总结、反思
- 要学会从身边各种事物中找到做题的灵感。
2.3
闲话
- \(huge\) 也表示能不穿校服就不穿校服,
- 高三的也放假了,中午 \(huge\) 没有通知去哪个食堂吃饭,但跟着一大群高一的去了小食堂,总共 \(4\) 个窗口,有点拥挤。看来以后去食堂得加快速度了。
- 又被通知搬宿舍,搬到了 \(9513\) ,貌似隔壁就是宿管的宿舍。搬完宿舍后去吃饭,看见了转生奥的 @reach_the_top ,还有我在家那边乡村中学才看到的现象——不顾旁边桌子上还有人在吃饭,直接踩着桌子过去。
做题纪要
luogu P3306 [SDOI2013] 随机数生成器
-
多倍经验: [ABC270G] Sequence in mod P | Gym103486C Random Number Generator
-
递推式为 \(x_{n}=(ax_{n-1}+b) \bmod p\) ,发现可以统一消去 \(\bmod p\) ,只在最后参与计算。以下过程省去模运算。
-
当 \(x_{1}=t\) 时,则 \(n=1\) 即为所求。
-
当 \(a=0,x_{1} \ne t\) 时,递推式转化为 \(x_{n}=b \bmod p\) 。若 \(b=t\) ,则 \(n=2\) 即为所求;否则无解。
-
当 \(a \ne 0,x_{1} \ne t\) 时,设 \(x_{n}+c=a(x_{n-1}+c)\) 。
- 当 \(a=1\) 时,递推式转化为 \(\begin{aligned} x_{n}&=x_{n-1}+b \\ &=x_{n-2}+2b \\ &=x_{n-3}+3b \\ &= \dots \\ &=x_{1}+(n-1)b \end{aligned}\) ,将 \(x_{n}=t\) 代入得 \(t=(x_{1}+(n-1)b) \bmod p\) ,移项得 \((n-1)b \equiv (t-x_{1}) \pmod {p}\) 。当 \(\gcd(b,p)|(t-x_{1})\) 时,移项得 \(n-1 \equiv \dfrac{t-x_{1}}{b} \pmod {p}\) ,解得 \(n \equiv \dfrac{t-x_{1}}{b}+1 \pmod {p}\) ;否则无解。
- 当 \(a \ne 1\) 时,解得 \(c=\dfrac{b}{a-1}\) 并和 \(x_{n}=t\) 一起代入原式得 \(\begin{aligned} t+\dfrac{b}{a-1}&=a(x_{n-1}+\dfrac{b}{a-1}) \\ &=a^{2}(x_{n-2}+\dfrac{b}{a-1}) \\ &=a^{3}(x_{n-3}+\dfrac{b}{a-1}) \\ &= \dots \\ &=a^{n-1}(x_{1}+\dfrac{b}{a-1}) \end{aligned}\) 。当 \(\gcd(x_{1}+\dfrac{b}{a-1},p)|(t+\dfrac{b}{a-1})\) 时,移项得 \(a^{n-1} \equiv \dfrac{t+\dfrac{b}{a-1}}{x_{1}+\dfrac{b}{a-1}} \pmod {p}\) ,跑遍 \(BSGS\) 求解即可;否则无解。
点击查看代码
ll qpow(ll a,ll b,ll p) { ll ans=1; while(b>0) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; } ll bsgs(ll a,ll b,ll p) { if(1%p==b%p) { return 0; } else { map<ll,ll>vis; ll k=sqrt(p)+1,i,sum; for(i=0;i<=k-1;i++) { vis[b*qpow(a,i,p)%p]=i; } a=qpow(a,k,p); for(i=0;i<=k;i++) { sum=qpow(a,i,p); if(vis.find(sum)!=vis.end()) { if(i*k-vis[sum]>=0) { return i*k-vis[sum]; } } } return -1; } } int main() { ll t,p,a,b,x1,day,ans,sum,i; cin>>t; for(i=1;i<=t;i++) { cin>>p>>a>>b>>x1>>day; if(x1==day) { cout<<1<<endl; } else { if(a==0) { cout<<((b==day)?2:-1)<<endl; } else { if(a==1) { if((day-x1)%gcd(b,p)==0) { cout<<qpow(b,p-2,p)*(day-x1+p)%p+1<<endl; } else { cout<<-1<<endl; } } else { sum=qpow(a-1,p-2,p)*b%p;; if((day+sum)%gcd(x1+sum,p)==0) { ans=bsgs(a,((day+sum)%p)*qpow(x1+sum,p-2,p)%p,p); cout<<((ans==-1)?ans:ans+1)<<endl; } else { cout<<-1<<endl; } } } } } return 0; }
luogu P3478 [POI2008]STA-Station
-
钦定 \(1\) 为根节点。
-
第一遍 \(DFS\) 时,设 \(f_{x}\) 表示以 \(x\) 为根的子树内的所有节点到 \(x\) 的距离之和,即 \(f_{x}=\sum\limits_{y \in Son(x)}(f_{y}+size_{y})\) 。
-
第二遍 \(DFS\) 时,进行换根。设 \(g_{x}\) 表示以 \(x\) 为整棵树的根节点时,所有节点到 \(x\) 的距离之和,仍钦定 \(1\) 为根节点。考虑根节点由 \(fa_{x}\) 变为 \(x\) 的过程中,以 \(x\) 为根的子树内的 \(size_{x}\) 个节点对答案的贡献会减少 \(1\) ,以 \(fa_{x}\) 为根节点的树内除了以 \(x\) 为根的子树内的 \(n-size_{x}\) 个节点对答案的贡献会增加 \(1\) ,状态转移方程为 \(g_{x}=\begin{cases} f_{x} & x=1 \\ g_{fa_{x}}+n-size_{x}-size_{x} & x \ne 1 \end{cases}\) 。
点击查看代码
struct node { ll nxt,to; }e[2000001]; ll head[2000001],siz[2000001],f[2000001],g[2000001],cnt=0; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(ll x,ll fa) { siz[x]=1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); f[x]+=f[e[i].to]+siz[e[i].to]; siz[x]+=siz[e[i].to]; } } } void reroot(ll x,ll fa,ll n) { if(x!=1) { g[x]=g[fa]+n-siz[x]-siz[x]; } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { reroot(e[i].to,x,n); } } } int main() { ll n,u,v,maxx=0,id=0,i; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs(1,0); g[1]=f[1]; reroot(1,0,n); for(i=1;i<=n;i++) { if(g[i]>maxx) { maxx=g[i]; id=i; } } cout<<id<<endl; return 0; }
CF1187E Tree Painting
-
发现在将第一个白色变成黑点后,其他将白色变为黑色的顺序并不会影响答案。每次染色,实际得到的贡献为以该节点为根的子树大小。
-
钦定 \(1\) 为根节点。
-
第一遍 \(DFS\) 时,设 \(f_{x}\) 表示先将 \(x\) 变成黑色后,以 \(x\) 为根的子树内的所有节点对 \(x\) 的贡献之和,即 \(f_{x}=1+\sum\limits_{y \in Son(x)}(f_{y}+size_{y})\) 。
-
第二遍 \(DFS\) 时,进行换根。设 \(g_{x}\) 表示以 \(x\) 为整棵树的根节点时,先将 \(x\) 变成黑色后,以 \(x\) 为根的子树内的所有节点对 \(x\) 的贡献之和,仍钦定 \(1\) 为根节点。考虑根节点由 \(fa_{x}\) 变为 \(x\) 的过程中,以 \(x\) 为根的子树内的 \(size_{x}\) 个节点对答案的贡献会减少 \(1\) ,以 \(fa_{x}\) 为根节点的树内除了以 \(x\) 为根的子树内的 \(n-size_{x}\) 个节点对答案的贡献会增加 \(1\) ,状态转移方程为 \(g_{x}=\begin{cases} f_{x} & x=1 \\ g_{fa_{x}}+n-size_{x}-size_{x} & x \ne 1 \end{cases}\) 。
点击查看代码
struct node { ll nxt,to; }e[400001]; ll head[400001],siz[400001],f[400001],g[400001],cnt=0; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(ll x,ll fa) { siz[x]=1; f[x]=1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); f[x]+=f[e[i].to]+siz[e[i].to]; siz[x]+=siz[e[i].to]; } } } void reroot(ll x,ll fa,ll n) { if(x!=1) { g[x]=g[fa]+n-siz[x]-siz[x]; } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { reroot(e[i].to,x,n); } } } int main() { ll n,u,v,ans=0,i; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs(1,0); g[1]=f[1]; reroot(1,0,n); for(i=1;i<=n;i++) { ans=max(ans,g[i]); } cout<<ans<<endl; return 0; }
luogu P2986 [USACO10MAR] Great Cow Gathering G
-
发现有了点权和边权,但没有太大变化。把点权拆成此处有几个点,计入 \(size\) 计算中。
-
钦定 \(1\) 为根节点。
-
第一遍 \(DFS\) 时,设 \(f_{x}\) 表示以 \(x\) 为根的子树内的所有节点到 \(x\) 的距离之和,即 \(f_{x}=\sum\limits_{y \in Son(x)}(f_{y}+size_{y} \times w_{x,y})\) 。
-
第二遍 \(DFS\) 时,进行换根。设 \(g_{x}\) 表示以 \(x\) 为整棵树的根节点时,所有节点到 \(x\) 的距离之和,仍钦定 \(1\) 为根节点。考虑根节点由 \(fa_{x}\) 变为 \(x\) 的过程中,以 \(x\) 为根的子树内的 \(size_{x}\) 个节点对答案的贡献会减少 \(w_{fa,x}\) ,以 \(fa_{x}\) 为根节点的树内除了以 \(x\) 为根的子树内的 \(n-size_{x}\) 个节点对答案的贡献会增加 \(w_{fa,x}\) ,状态转移方程为 \(g_{x}=\begin{cases} f_{x} & x=1 \\ g_{fa_{x}}+(n-size_{x}) \times w_{fa_{x},x}-size_{x} \times w_{fa_{x},x}& x \ne 1 \end{cases}\) 。
点击查看代码
struct node { ll nxt,to,w; }e[200001]; ll head[200001],siz[200001],f[200001],g[200001],c[200001],cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs(ll x,ll fa) { siz[x]=c[x]; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); f[x]+=f[e[i].to]+siz[e[i].to]*e[i].w; siz[x]+=siz[e[i].to]; } } } void reroot(ll x,ll fa,ll n,ll w) { if(x!=1) { g[x]=g[fa]+(n-siz[x])*w-siz[x]*w; } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { reroot(e[i].to,x,n,e[i].w); } } } int main() { ll n,u,v,w,sum=0,minn=0x7f7f7f7f7f7f7f7f,i; cin>>n; for(i=1;i<=n;i++) { cin>>c[i]; sum+=c[i]; } for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } dfs(1,0); g[1]=f[1]; reroot(1,0,sum,0); for(i=1;i<=n;i++) { minn=min(minn,g[i]); } cout<<minn<<endl; return 0; }
POJ3585 Accumulation Degree
-
钦定 \(1\) 为根节点。
-
第一遍 \(DFS\) 时,设 \(f_{x}\) 表示以 \(x\) 为根的子树内,当 \(x\) 作为源点,从 \(x\) 出发流向子树的流量的总和的最大值,即 \(f_{x}=\sum\limits_{y \in Son(x)}\begin{cases} c_{x,y} & din_{y}=1 \\ \min(f_{y},c_{x,y}) & din_{y} \ne 1 \end{cases}\) 。
-
第二遍 \(DFS\) 时,进行换根。设 \(g_{x}\) 表示以 \(x\) 为整棵树的根节点时,当 \(x\) 作为源点,从 \(x\) 出发流向整个水系的流量的总和的最大值。考虑根节点由 \(fa_{x}\) 变为 \(x\) 的过程中,原从 \(fa_{x}\) 流向 \(x\) 的流量为 \(\begin{cases} \min(f_{x},c_{fa_{x},x}) & din_{x} \ne 1 \\ c_{fa_{x},x} & din_{x}=1 \end{cases}\) ,流向除以 \(x\) 为根节点的子树外的其他子树的流量为 \(g_{fa_{x}}-\begin{cases} \min(f_{x},c_{fa_{x},x}) & din_{x} \ne 1 \\ c_{fa_{x},x} & din_{x}=1 \end{cases}\) ;先从 \(x\) 流向 \(fa_{x}\) 再流向除以 \(x\) 为根节点的子树外的其他子树的流量为 \(\begin{cases} \min(c_{x,fa_{x}},g_{fa_{x}}-\begin{cases} \min(f_{x},c_{fa_{x},x}) & din_{x} \ne 1 \\ c_{fa_{x},x} & din_{x}=1 \end{cases}) & din_{fa_{x}} \ne 1 \\ c_{x,fa_{x}} & din_{fa_{x}=1} \end{cases}\) 。状态转移方程为 \(g_{x}=f_{x}+\begin{cases} c_{x,fa_{x}} & din_{fa_{x}}=1 \\ \min(c_{x,fa_{x}},g_{fa_{x}}-\min(f_{x},c_{fa_{x},x})) & din_{fa_{x}} \ne 1,din_{x} \ne 1 \\ \min(c_{x,fa_{x}},g_{fa_{x}}-c_{fa_{x},x}) & din_{fa_{x}} \ne 1,din_{x} =1 \end{cases}\) 。
点击查看代码
struct node { ll nxt,to,w; }e[400001]; ll head[400001],f[400001],g[400001],din[400001],cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs(ll x,ll fa) { for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); f[x]+=(din[e[i].to]==1)?e[i].w:min(f[e[i].to],e[i].w); } } } void reroot(ll x,ll fa,ll w) { if(x!=1) { g[x]=f[x]+((din[fa]==1)?w:min(g[fa]-((din[x]==1)?w:min(f[x],w)),w)); } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { reroot(e[i].to,x,e[i].w); } } } int main() { ll t,n,u,v,w,maxx,i,j; cin>>t; for(i=1;i<=t;i++) { cnt=maxx=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); memset(din,0,sizeof(din)); cin>>n; for(j=1;j<=n-1;j++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); din[u]++; din[v]++; } dfs(1,0); g[1]=f[1]; reroot(1,0,0); for(j=1;j<=n;j++) { maxx=max(maxx,g[j]); } cout<<maxx<<endl; } return 0; }
HDU2196 Computer
-
钦定 \(1\) 为根节点。
-
令 \(f_{x},cmax_{x},g_{x}\) 分别表示节点 \(x\) 到以 \(x\) 为根的子树内的叶子节点的最长距离、次长距离和节点 \(x\) 到除以 \(x\) 为根的子树内的叶子节点的最长距离。
-
第一遍 \(DFS\) 时,处理出 \(f_{x},cmax_{x}\) ,并记录到以 \(x\) 为根的子树内的叶子节点的最长距离时的路径上的叶子节点 \(path\) 。
-
第二遍 \(DFS\) 时,有 \(g_{x}=\begin{cases} 0 & x=1 \\ w_{fa_{x},x}+\max(cmax_{fa_{x}},g_{fa_{x}}) & x=path_{fa_{x}} \\ w_{fa_{x},x}+\max(f_{fa_{x}},g_{fa_{x}}) & x \ne 1,x \ne path_{fa_{x}} \end{cases}\) 。
点击查看代码
struct node { ll nxt,to,w; }e[20001]; ll head[20001],f[20001],g[20001],cmax[20001],path[20001],cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs(ll x,ll fa) { for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); if(f[e[i].to]+e[i].w>f[x]) { cmax[x]=f[x]; f[x]=f[e[i].to]+e[i].w; path[x]=e[i].to; } else { cmax[x]=max(cmax[x],f[e[i].to]+e[i].w); } } } } void reroot(ll x,ll fa,ll w) { if(x!=1) { g[x]=w+((x==path[fa])?max(cmax[fa],g[fa]):max(f[fa],g[fa])); } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { reroot(e[i].to,x,e[i].w); } } } int main() { ll n,u,v,w,i; while(cin>>n) { cnt=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); memset(cmax,0,sizeof(cmax)); memset(path,0,sizeof(path)); for(i=2;i<=n;i++) { u=i; cin>>v>>w; add(u,v,w); add(v,u,w); } dfs(1,0); g[1]=0; reroot(1,0,0); for(i=1;i<=n;i++) { cout<<max(f[i],g[i])<<endl; } } return 0; }
每日总结、反思
- 对于一些看似乱搞做法,要理解其与正解之间的本质关系。
- 全面分析问题,分讨不要少情况。
2.4
闲话
- 中午因跟着高二的提前 \(3min\) 去吃饭,下午就被 \(huge\) \(D\) 了。
做题纪要
luogu P6419 [COCI2014-2015#1] Kamp
-
钦定 \(1\) 为根节点。
-
令 \(f_{x},zmax_{x},cmax_{x},g_{x}\) 分别表示从节点 \(x\) 出发把以 \(x\) 为根节点的子树内的所有人送回去并回到 \(x\) 的最少时间,节点 \(x\) 到以 \(x\) 为根的子树内的叶子节点的最长距离、次长距离,当 \(x\) 为整棵树的根节点时从节点 \(x\) 出发把以 \(x\) 为根节点的子树内的所有人送回去并回到 \(x\) 的最少时间。
-
第一遍 \(DFS\) 时,处理出 \(f_{x},zmax_{x},cmax_{x}\) 。具体的,有 \(f_{x}=\sum\limits_{y \in Son(x)}[size_{y} \ge 1] \times (f_{y}+2w_{x,y})\) 。
-
第二遍 \(DFS\) 时,进行大分讨和进行换根。设 \(g_{x}\) 表示以 \(x\) 为整棵树的根节点时,从节点 \(x\) 出发把以 \(x\) 为根节点的子树内的所有人送回去并回到 \(x\) 的最少时间,仍钦定 \(1\) 为根节点。考虑根节点由 \(fa_{x}\) 变为 \(x\) 的过程中,当 \(size_{x}=k\) 时有 \(g_{x}=f_{x}\) ;当 \(size_{x}=0\) 时有 \(g_{x}=g_{fa_{x}}+2w_{x,fa_{x}}\) ;否则,从 \(fa_{x}\) 出发把除以 \(x\) 为根节点的子树外的所有人送回去并回到 \(fa_{x}\) 的最少时间为 \(g_{fa_{x}}-f_{x}-2w_{fa_{x},x}\) ,再从 \(fa_{x}\) 到达 \(x\) 需要再消耗 \(2w_{x,fa_{x}}\) 的时间。故状态转移方程为 \(g_{x}=\begin{cases} f_{x} & x=1 \\ f_{x} & x \ne 1,size_{x}=k \\ g_{fa_{x}}+2w_{x,fa_{x}} & x \ne 1,size_{x}=0 \\ g_{fa_{x}}-f_{x}-2w_{fa_{x},x}+2w_{x,fa_{x}}+f_{x} & x \ne 1,size_{x} \ne 0,size_{x} \ne k \end{cases}\) 。同时记录当 \(x\) 为整棵树的根节点时,节点 \(x\) 到以 \(x\) 为根的子树内的叶子节点的最长距离 \(zmaxx_{x}\) 。
-
从贪心的角度来看,最长距离我们只走一遍,故 \(g_{i}-\max(zmax_{i},zmaxx_{i})\) 即为所求。
点击查看代码
struct node { ll nxt,to,w; }e[1000001]; ll head[1000001],siz[1000001],f[1000001],g[1000001],zmax[1000001],zmaxx[1000001],cmax[1000001],sum[1000001],cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs(ll x,ll fa) { siz[x]=(sum[x]==1); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); siz[x]+=siz[e[i].to]; if(siz[e[i].to]!=0) { f[x]+=f[e[i].to]+2*e[i].w; if(zmax[e[i].to]+e[i].w>zmax[x]) { cmax[x]=zmax[x]; zmax[x]=zmax[e[i].to]+e[i].w; } else { cmax[x]=max(cmax[x],zmax[e[i].to]+e[i].w); } } } } } void reroot(ll x,ll fa,ll m,ll w) { if(x!=1) { if(siz[x]==m) { g[x]=f[x]; zmaxx[x]=zmax[x]; } else { g[x]=(siz[x]==0)?g[fa]+2*w:g[fa]-f[x]-2*w+2*w+f[x]; zmaxx[x]=w+((zmax[x]+w==zmax[fa])?max(zmaxx[fa],cmax[fa]):max(zmaxx[fa],zmax[fa])); } } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { reroot(e[i].to,x,m,e[i].w); } } } int main() { ll n,m,u,v,w,a,i; cin>>n>>m; for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } for(i=1;i<=m;i++) { cin>>a; sum[a]=1; } dfs(1,0); g[1]=f[1]; reroot(1,0,m,0); for(i=1;i<=n;i++) { cout<<g[i]-max(zmax[i],zmaxx[i])<<endl; } return 0; }
AcWing 104. 货仓选址
-
简化题意:给定长度为 \(n\) 的序列 \(\{ a \}\) ,求 \(\min \{ \sum\limits_{i=1}^{n}|a_{i}-x| \}\) 。
-
将 \(a_{1} \sim a_{n}\) 进行排序。设比 \(x\) 小的有 \(p\) 个,比 \(x\) 大的有 \(q\) 个。若 \(p<q\) ,则 \(x\) 每增加 \(1\) , \(a_{1} \sim a_{p}\) 对答案的贡献会增加 \(1\) , \(a_{n-q+1} \sim a_{n}\) 对答案的贡献会减少 \(1\) ,总贡献会减少 \(q-p\) ;若 \(p>q\) ,则 \(x\) 每减少 \(1\) , \(a_{1} \sim a_{p}\) 对答案的贡献会减少 \(1\) , \(a_{n-q+1} \sim a_{n}\) 对答案的贡献会增加 \(1\) ,总贡献会减少 \(p-q\) 。最终当 \(p=q\) 时取到最小值,即 \(x\) 取 \(\{ a \}\) 的中位数(非严格意义)时取到最小值。具体的,将 \(a_{1} \sim a_{n}\) 进行排序后,若 \(n\) 为奇数,则 \(x=a_{\left\lceil \frac{n}{2} \right\rceil}=a_{\frac{1+n}{2}}\) 时取到最小值;若 \(n\) 为偶数,有 \(\left\lfloor \dfrac{1+n}{2} \right\rfloor=\dfrac{n}{2}\) ,则 \(x \in [a_{\frac{n}{2}},a_{\frac{n}{2}+1}]\) 时取到最小值。
点击查看代码
ll a[100001]; int main() { ll n,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } sort(a+1,a+1+n); for(i=1;i<=n;i++) { ans+=abs(a[i]-a[(ll)ceil(1.0*n/2)]); } cout<<ans<<endl; return 0; }
CF523D Statistics of Recompressing Videos
-
priority_queue
大法好。点击查看代码
priority_queue<ll>q; int main() { ll n,k,s,m,i,ans; scanf("%lld%lld",&n,&k); for(i=1;i<=n;i++) { scanf("%lld%lld",&s,&m); ans=s+m; if(q.empty()==0) { if(q.size()<=k-1) { q.push(-ans); } else { ans=(s>=-q.top())?ans:-q.top()+m; q.pop(); q.push(-ans); } } else { q.push(-ans); } printf("%lld\n",ans); } return 0; }
luogu P4677 山区建小学
-
将每两个村镇之间的距离 \(d\) 转化成到第一个城镇的距离 \(a\) 。
-
令 \(f_{i,j},g_{i,j}\) 表示分别表示 \(1 \sim i\) 个村镇中建了 \(j\) 个小学的最小距离总和, \(i \sim j\) 个村镇中建了 \(1\) 个小学的最小距离总和。
-
因 \(\{ a \}\) 此时已经是有序的了,故有 \(g_{i,j}=\sum\limits_{k=i}^{j}|a_{k}-a_{\left\lfloor \frac{i+j}{2} \right\rfloor}|,f_{i,j}=\min\limits_{k=j-1}^{i-1} \{ f_{k,j}+g_{k+1,i}\}\) 。
-
\(f\) 数组除 \(f_{0,0}=0\) 外初始化为 \(\infty\) 。最终 \(f_{n,m}\) 即为所求。
点击查看代码
int a[501],f[501][501],g[501][501]; int main() { int n,m,i,j,k; cin>>n>>m; memset(f,0x3f,sizeof(f)); for(i=2;i<=n;i++) { cin>>a[i]; a[i]+=a[i-1]; } for(i=1;i<=n;i++) { for(j=i;j<=n;j++) { for(k=i;k<=j;k++) { g[i][j]+=abs(a[k]-a[(i+j)/2]); } } } f[0][0]=0; for(i=1;i<=n;i++) { for(j=1;j<=min(i,m);j++) { for(k=j-1;k<=i-1;k++) { f[i][j]=min(f[i][j],f[k][j-1]+g[k+1][i]); } } } cout<<f[n][m]<<endl; return 0; }
POJ1723 SOLDIERS
-
\(y\) 的处理和普通货仓选址一样。
-
将 \(x\) 排序后,为方便处理,将第 \(i\) 个士兵放在第 \(i\) 个位置。设起点为 \(xx\) ,等价于求 \(\min \{ \sum\limits_{i=1}^{n}|x_{i}-(xx+i-1)| \}=\min \{ \sum\limits_{i=1}^{n}|x_{i}-i+1-xx| \}\) ,然后处理就和普通货仓选址一样了。
点击查看代码
int x[10001],y[10001]; int main() { int n,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>x[i]>>y[i]; } sort(y+1,y+1+n); for(i=1;i<=n;i++) { ans+=abs(y[i]-y[(ll)ceil(1.0*n/2)]); } sort(x+1,x+1+n); for(i=1;i<=n;i++) { x[i]+=-i+1; } sort(x+1,x+1+n); for(i=1;i<=n;i++) { ans+=abs(x[i]-x[(ll)ceil(1.0*n/2)]); } cout<<ans<<endl; return 0; }
HZOJ 862. 高爸
- 详见 2024初三年前集训测试2 T3 高爸 。
LibreOJ 100. 矩阵乘法
-
多倍经验: luogu B2105 矩阵乘法 | luogu B3615 测测你的矩阵乘法 | LibreOJ 10219. 「一本通 6.5 例 1」矩阵 A×B
-
按题意模拟。
点击查看代码
struct Matrix { ll ma[501][501]; Matrix() { memset(ma,0,sizeof(ma)); } }a,b,c; Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p) { Matrix c; for(ll i=1;i<=n;i++) { for(ll j=1;j<=k;j++) { for(ll h=1;h<=m;h++) { c.ma[i][j]=(c.ma[i][j]+((a.ma[i][h]+p)%p)*((b.ma[h][j]+p)%p)%p)%p; } } } return c; } int main() { ll n,m,k,p=1e9+7,i,j; cin>>n>>m>>k; for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { cin>>a.ma[i][j]; } } for(i=1;i<=m;i++) { for(j=1;j<=k;j++) { cin>>b.ma[i][j]; } } c=mul(a,b,n,m,k,p); for(i=1;i<=n;i++) { for(j=1;j<=k;j++) { cout<<c.ma[i][j]<<" "; } cout<<endl; } return 0; }
luogu P3390 【模板】矩阵快速幂
-
矩阵快速幂板子。
-
记得定义单位矩阵 \(I= \begin{bmatrix} 1 & 0 & \cdots & 0 \\ 0 & 1 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & 1 \end{bmatrix}\) 。
点击查看代码
struct Matrix { ll ma[101][101]; Matrix() { memset(ma,0,sizeof(ma)); } }a,c; inline Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p) { Matrix c; for(ll i=1;i<=n;i++) { for(ll j=1;j<=k;j++) { for(ll h=1;h<=m;h++) { c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p; } } } return c; } inline Matrix qpow(Matrix a,ll b,ll p,ll n) { Matrix ans; for(ll i=1;i<=n;i++) { ans.ma[i][i]=1; } while(b>0) { if(b&1) { ans=mul(ans,a,n,n,n,p); } b>>=1; a=mul(a,a,n,n,n,p); } return ans; } int main() { ll n,b,p=1000000007,i,j; cin>>n>>b; for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { cin>>a.ma[i][j]; } } c=qpow(a,b,p,n); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { cout<<c.ma[i][j]<<" "; } cout<<endl; } return 0; }
luogu P1962 斐波那契数列
-
多倍经验: LibreOJ 10220. 「一本通 6.5 例 2」Fibonacci 第 n 项 | LibreOJ 10223. 「一本通 6.5 练习 1」Fibonacci
-
令 \(F_{n}= \begin{bmatrix} Fib_{n} & Fib_{n+1}\end{bmatrix}\) ,容易有 \(\begin{aligned}F_{n} &= \begin{bmatrix} Fib_{n} & Fib_{n+1}\end{bmatrix} \\ &= \begin{bmatrix} Fib_{n-1} & Fib_{n}\end{bmatrix} \times \begin{bmatrix} 0 & 1 \\ 1 & 1\end{bmatrix} \\ &= \begin{bmatrix} Fib_{n-2} & Fib_{n-1}\end{bmatrix} \times \begin{bmatrix} 0 & 1 \\ 1 & 1\end{bmatrix}^{2} \\ &= \begin{bmatrix} Fib_{n-3} & Fib_{n-2}\end{bmatrix} \times \begin{bmatrix} 0 & 1 \\ 1 & 1\end{bmatrix}^{3} \\ &= \dots \\ &= \begin{bmatrix} Fib_{0} & Fib_{1}\end{bmatrix} \times \begin{bmatrix} 0 &1 \\ 1 & 1\end{bmatrix}^{n} \\ &= F_{0} \times \begin{bmatrix} 0 &1 \\ 1 & 1\end{bmatrix}^{n} \end{aligned}\) 。
点击查看代码
struct Matrix { ll ma[5][5]; Matrix() { memset(ma,0,sizeof(ma)); } }f,a; Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p) { Matrix c; for(ll i=1;i<=n;i++) { for(ll j=1;j<=k;j++) { for(ll h=1;h<=m;h++) { c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p; } } } return c; } Matrix qpow(Matrix a,ll b,ll p,ll n) { Matrix ans; for(ll i=1;i<=n;i++) { ans.ma[i][i]=1; } while(b>0) { if(b&1) { ans=mul(ans,a,n,n,n,p); } b>>=1; a=mul(a,a,n,n,n,p); } return ans; } int main() { ll b,n=1,m=2,k=2,p=1000000007; cin>>b; f.ma[1][1]=0; f.ma[1][2]=1; a.ma[1][1]=0; a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1; cout<<mul(f,qpow(a,b,p,m),n,m,k,p).ma[1][1]<<endl; return 0; }
LibreOJ 10221. 「一本通 6.5 例 3」Fibonacci 前 n 项和
-
由 \(Fib_{1}=Fib_{3}-Fib_{2},Fib_{2}=Fib_{4}-Fib_{3},Fib_{3}=Fib_{5}-Fib_{4}, \dots ,Fib_{n}=Fib_{n+2}-Fib_{n+1}\) ,有 \(\begin{aligned} S_{n} &=\sum\limits_{i=1}^{n}Fib_{i} \\ &= \sum\limits_{i=1}^{n}Fib_{i+2}-Fib_{i+1} \\ &=\sum\limits_{i=1}^{n}Fib_{i+2}-\sum\limits_{i=1}^{n}Fib_{i+1} \\ &=\sum\limits_{i=3}^{n+2}Fib_{i}-\sum\limits_{i=2}^{n+1}Fib_{i} \\ &=Fib_{n+2}-Fib_{2} \\ &=Fib_{n+2}-1\end{aligned}\) 。
点击查看代码
struct Matrix { ll ma[5][5]; Matrix() { memset(ma,0,sizeof(ma)); } }f,a; Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p) { Matrix c; for(ll i=1;i<=n;i++) { for(ll j=1;j<=k;j++) { for(ll h=1;h<=m;h++) { c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p; } } } return c; } Matrix qpow(Matrix a,ll b,ll p,ll n) { Matrix ans; for(ll i=1;i<=n;i++) { ans.ma[i][i]=1; } while(b>0) { if(b&1) { ans=mul(ans,a,n,n,n,p); } b>>=1; a=mul(a,a,n,n,n,p); } return ans; } int main() { ll b,p,n=1,m=2,k=2; cin>>b>>p; f.ma[1][1]=0; f.ma[1][2]=1; a.ma[1][1]=0; a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1; cout<<(mul(f,qpow(a,b+2,p,m),n,m,k,p).ma[1][1]-1+p)%p<<endl; return 0; }
每日总结、反思
- 用
scanf,printf
时一定要看清类型名,不要出现scanf("%dll",&...)
或一个long long
拿%ld
来读的现象,会出离奇问题。 - 编译指令记得开全部警告。
- 当输出变得离奇(包括但不限于每隔一段时间换一个答案)时,要记得回去看初始化。
2.5
闲话
- \(miaomiao\) 上午 \(7:50 \sim 12:10\) 安排了一场模拟赛,这次把网关了。
- 题解: 2024初三年前集训测试3 。
- 不知道为什么 \(1\) 机房又把 Acwing 封了。
- 中午吃饭的时候,又一次看见了 @reach_the_top ,和他搭了几句话。
做题纪要
LibreOJ 10222. 「一本通 6.5 例 4」佳佳的 Fibonacci
-
推式子。
-
\(\begin{aligned} T_{n} &= \sum\limits_{i=1}^{n}i \times Fib_{i} \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}Fib_{j} \\ &=\sum\limits_{i=1}^{n}(S_{n}-S_{i-1}) \\ &=nS_{n}-\sum\limits_{i=1}^{n}S_{i-1} \\ &=n(Fib_{n+2}-1)-\sum\limits_{i=1}^{n}(Fib_{i+1}-1) \\ &=n \times Fib_{n+2}-\sum\limits_{i=2}^{n+1}Fib_{i} \\ &=n \times Fib_{n+2}-(S_{n}-S_{1}) \\ &=n \times Fib_{n+2}-S_{n+1}+Fib_{1} \\ &=n \times Fib_{n+2}-(Fib_{n+3}-1)+1 \\ &=n \times Fib_{n+2}-Fib_{n+3}+2 \end{aligned}\)
点击查看代码
struct Matrix { ll ma[5][5]; Matrix() { memset(ma,0,sizeof(ma)); } }f,a; Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p) { Matrix c; for(ll i=1;i<=n;i++) { for(ll j=1;j<=k;j++) { for(ll h=1;h<=m;h++) { c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p; } } } return c; } Matrix qpow(Matrix a,ll b,ll p,ll n) { Matrix ans; for(ll i=1;i<=n;i++) { ans.ma[i][i]=1; } while(b>0) { if(b&1) { ans=mul(ans,a,n,n,n,p); } b>>=1; a=mul(a,a,n,n,n,p); } return ans; } int main() { ll b,p,n=1,m=2,k=2,sum1,sum2; cin>>b>>p; f.ma[1][1]=0; f.ma[1][2]=1; a.ma[1][1]=0; a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1; sum1=mul(f,qpow(a,b+2,p,m),n,m,k,p).ma[1][1]*b%p; f.ma[1][1]=0; f.ma[1][2]=1; a.ma[1][1]=0; a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1; sum2=mul(f,qpow(a,b+3,p,m),n,m,k,p).ma[1][1]; cout<<(sum1-sum2+2+p)%p; return 0; }
HDU1575 Tr A
-
矩阵快速幂板子。
-
额外知识点
- 主对角线:在 \(n\) 阶行列式中从左上角到右下角的对角线。
- 迹:在 \(n\) 阶行列式中主对角线上元素之和。
- 次对角线:在 \(n\) 阶行列式中从右上角到左下角的对角线。
点击查看代码
struct Matrix { ll ma[15][15]; Matrix() { memset(ma,0,sizeof(ma)); } }a,c; Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p) { Matrix c; for(ll i=1;i<=n;i++) { for(ll j=1;j<=k;j++) { for(ll h=1;h<=m;h++) { c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p; } } } return c; } Matrix qpow(Matrix a,ll b,ll p,ll n) { Matrix ans; for(ll i=1;i<=n;i++) { ans.ma[i][i]=1; } while(b>0) { if(b&1) { ans=mul(ans,a,n,n,n,p); } b>>=1; a=mul(a,a,n,n,n,p); } return ans; } int main() { ll t,n,b,ans,i,j,k,p=9973; cin>>t; for(i=1;i<=t;i++) { cin>>n>>b; ans=0; for(j=1;j<=n;j++) { for(k=1;k<=n;k++) { cin>>a.ma[j][k]; } } c=qpow(a,b,p,n); for(j=1;j<=n;j++) { ans=(ans+c.ma[j][j]%p)%p; } cout<<ans<<endl; } return 0; }
tgHZOJ 264. 选拔队员
-
设有 \(i(0 \le i \le n)\) 个女生, \(n-i\) 个男生。
-
\(n-i\) 个男生中,有 \(i-1\) 个男生需要各放在每两个女生之间,剩余 \(n-i-(i-1)\) 个男生可以任意放,此时等价于求 \(\sum\limits_{j=1}^{i+1}x_{j}=n-i-(i-1)\) 的非负整数解数量 \(\dbinom{n-i-(i-1)+i+1-1}{i+1-1}=\dbinom{n-i+1}{i}\) 。
-
由打表,有 \(\begin{aligned} \sum\limits_{i=0}^{n}\dbinom{n-i+1}{i} &=Fib_{n+2} \end{aligned}\) 即为所求。
- 证明
- 数学归纳法。
- 当 \(n=1,n=2\) 时,有命题成立。
- 假设当 \(n=m-1,m(2 \le m)\) 时命题成立。当 \(n=m+1\) 时,有 \(\\ \begin{aligned} \sum\limits_{i=0}^{m+1}\dbinom{m+1-i+1}{i} &=\sum\limits_{i=0}^{m+1}\dbinom{m-i+1}{i-1}+\dbinom{m-i+1}{i} \\ &=\sum\limits_{i=1}^{m+1}\dbinom{m-i+1}{i-1}+\sum\limits_{i=0}^{m+1}\dbinom{m-i+1}{i} \\ &=\sum\limits_{i=0}^{m+1}\dbinom{m-i}{i}+\sum\limits_{i=0}^{m+1}\dbinom{m-i+1}{i} \\ &=\sum\limits_{i=0}^{m}\dbinom{m-i}{i}+\sum\limits_{i=0}^{m}\dbinom{m-i+1}{i} \\ &=\sum\limits_{i=0}^{m-1}\dbinom{m-i}{i}+\sum\limits_{i=0}^{m}\dbinom{m-i+1}{i} \\ &=\sum\limits_{i=0}^{m-1}\dbinom{(m-1)-i+1}{i}+\sum\limits_{i=0}^{m}\dbinom{m-i+1}{i} \\ &=Fib_{m+1}+Fib_{m+2} \\ &=Fib_{m+3} \end{aligned}\) 。
- 故命题成立。
点击查看代码
struct Matrix { ll ma[5][5]; Matrix() { memset(ma,0,sizeof(Matrix)); } }f,a; Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p) { Matrix c; for(ll i=1;i<=n;i++) { for(ll j=1;j<=k;j++) { for(ll h=1;h<=m;h++) { c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p; } } } return c; } Matrix qpow(Matrix a,ll b,ll p,ll n) { Matrix ans; for(ll i=1;i<=n;i++) { ans.ma[i][i]=1; } while(b>0) { if(b&1) { ans=mul(ans,a,n,n,n,p); } b>>=1; a=mul(a,a,n,n,n,p); } return ans; } int main() { ll t,b,p,n=1,m=2,k=2,i; cin>>t>>p; for(i=1;i<=t;i++) { cin>>b; f.ma[1][1]=0; f.ma[1][2]=1; a.ma[1][1]=0; a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1; cout<<mul(f,qpow(a,b+2,p,m),n,m,k,p).ma[1][1]<<endl; } return 0; }
- 证明
HZOJ 864. 夕景昨日
HZOJ 865. 透明答案
HZOJ 866. 界外科学
HZOJ 867. 回忆补时
luogu P1306 斐波那契公约数
-
推式子。
-
令 \(n \ge m\) ,有 \(\begin{aligned} \gcd(Fib_{n},Fib_{m}) &=\gcd(Fib_{n-1}+Fib_{n-2},Fib_{m}) \\ &=\gcd(2Fib_{n-2}+Fib_{n-3},Fib_{m}) \\ &=\gcd(3Fib_{n-3}+2Fib_{n-4},Fib_{m}) \\ &=\gcd(5Fib_{n-4}+3Fib_{n-5},Fib_{m}) \\ &=\gcd(Fib_{5}Fib_{n-4}+Fib_{4}Fib_{n-5},Fib_{m}) \\ &=\dots \\ &=\gcd(Fib_{m+1}Fib_{n-m}+Fib_{m}Fib_{n-m-1},Fib_{m}) \\ &=\gcd(Fib_{m+1}Fib_{n-m},Fib_{m}) \\ &=\gcd(Fib_{n-m},Fib_{m}) \\ &=\gcd(Fib_{n \bmod m},Fib_{m}) \\ &=\gcd(Fib_{\gcd(n,m)},Fib_{\gcd(n,m)}) \\ &=Fib_{\gcd(n,m)} \end{aligned}\) 。
点击查看代码
struct Matrix { ll ma[5][5]; Matrix() { memset(ma,0,sizeof(ma)); } }f,a; Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p) { Matrix c; for(ll i=1;i<=n;i++) { for(ll j=1;j<=k;j++) { for(ll h=1;h<=m;h++) { c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p; } } } return c; } Matrix qpow(Matrix a,ll b,ll p,ll n) { Matrix ans; for(ll i=1;i<=n;i++) { ans.ma[i][i]=1; } while(b>0) { if(b&1) { ans=mul(ans,a,n,n,n,p); } b>>=1; a=mul(a,a,n,n,n,p); } return ans; } ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; } int main() { ll aa,bb,n=1,m=2,k=2,p=100000000; cin>>aa>>bb; f.ma[1][1]=0; f.ma[1][2]=1; a.ma[1][1]=0; a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1; cout<<mul(f,qpow(a,gcd(aa,bb),p,m),n,m,k,p).ma[1][1]<<endl; return 0; }
luogu P1168 中位数
-
对顶堆板子。
-
算法的核心思想是维护一个大根堆和小根堆,将大根堆倒置过来在小根堆的上方,使从上到下数字依次增大。序列内中位数是大、小根堆中大小较大的堆的堆顶。
点击查看代码
ll a[100001]; priority_queue<int,vector<int>,less<int> >qmin; priority_queue<int,vector<int>,greater<int> >qmax; int main() { ll n,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } qmin.push(a[1]); cout<<a[1]<<endl; for(i=2;i<=n;i++) { if(a[i]>qmin.top()) { qmax.push(a[i]); } else { qmin.push(a[i]); } while(abs((int)qmin.size()-(int)qmax.size())>=2) { if(qmin.size()>qmax.size()) { qmax.push(qmin.top()); qmin.pop(); } else { qmin.push(qmax.top()); qmax.pop(); } } if(i%2==1) { cout<<((qmin.size()>qmax.size())?qmin.top():qmax.top())<<endl; } } return 0; }
每日总结、反思
- 要学会猜测结论然后逆向证明的方式来尝试证明。
- 要学会正确的打表。
2.6
闲话
- 今天就要
放假回家集训了。 - 上午 \(field\) 称大课间的时候让我们出去活动活动,顺便调侃了我们一下。
做题纪要
luogu P1801 黑匣子
-
查询第 \(k\) 时应维护大根堆的大小始终为 \(k-1\),此时小根堆的堆顶即为所求。
点击查看代码
int a[200001]; priority_queue<int,vector<int>,less<int> >qmin; priority_queue<int,vector<int>,greater<int> >qmax; int main() { int n,m,last=0,u,i,j; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=m;i++) { cin>>u; for(j=last+1;j<=u;j++) { qmin.push(a[j]); } last=u; while(qmin.size()>=i) { qmax.push(qmin.top()); qmin.pop(); } cout<<qmax.top()<<endl; qmin.push(qmax.top()); qmax.pop(); } return 0; }
luogu P3871 [TJOI2010]中位数
-
规定若序列长度为偶数,则指处在中间位置的两个数中较小的一个,故输出大根堆的堆顶即可。
点击查看代码
ll a[100001]; priority_queue<int,vector<int>,less<int> >qmin; priority_queue<int,vector<int>,greater<int> >qmax; int main() { ll n,m,aa,i; string pd; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } qmin.push(a[1]); for(i=2;i<=n;i++) { if(a[i]>qmin.top()) { qmax.push(a[i]); } else { qmin.push(a[i]); } while(abs((int)qmax.size()-(int)qmin.size())>=2) { if(qmin.size()>qmax.size()) { qmax.push(qmin.top()); qmin.pop(); } else { qmin.push(qmax.top()); qmax.pop(); } } } cin>>m; for(i=1;i<=m;i++) { cin>>pd; if(pd=="add") { cin>>aa; n++; if(aa>qmin.top()) { qmax.push(aa); } else { qmin.push(aa); } while(abs((int)qmax.size()-(int)qmin.size())>=2) { if(qmin.size()>qmax.size()) { qmax.push(qmin.top()); qmin.pop(); } else { qmin.push(qmax.top()); qmax.pop(); } } } else { if(n%2==0) { cout<<qmin.top()<<endl; } else { cout<<((qmin.size()>qmax.size())?qmin.top():qmax.top())<<endl; } } } return 0; }
luogu P6033 [NOIP2004 提高组] 合并果子 加强版
-
luogu P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G 中我们使用堆来维护一堆数中最小的两个数。但本题中优先队列的插入耗时太大,考虑从优化插入入手。容易发现,插入的数具有单调性。
-
将原序列进行排序,并将排序后的数插入一个队列 \(q_{1}\) 里;每次合并时,在 \(q_{1}\) 和 \(q_{2}\) 里面选出最小的两个数,相加统计答案并插入 \(q_{2}\) 中。
点击查看代码
ll a[10000001],vis[100001]; queue<ll>q1,q2; ll read() { ll x=0,f=1; char c=getchar(); while(c>'9'||c<'0') { if(c=='-') { f=-1; } c=getchar(); } while('0'<=c&&c<='9') { x=x*10+c-'0'; c=getchar(); } return x*f; } void write(ll x) { if(x<0) { putchar('-'); x=-x; } if(x>9) { write(x/10); } putchar((x%10)+'0'); } int main() { ll n,x,y,ans=0,minn=0x7f7f7f7f,maxx=0,i,j; n=read(); for(i=1;i<=n;i++) { a[i]=read(); maxx=max(maxx,a[i]); minn=min(minn,a[i]); vis[a[i]]++; } for(i=minn;i<=maxx;i++) { for(j=1;j<=vis[i];j++) { q1.push(i); } } for(i=1;i<=n-1;i++)//n个数需要合并n-1次 { if(q2.empty()!=0||(q1.empty()==0&&q2.empty()==0&&q1.front()<q2.front())) { x=q1.front(); q1.pop(); } else { x=q2.front(); q2.pop(); } if(q2.empty()!=0||(q1.empty()==0&&q2.empty()==0&&q1.front()<q2.front())) { y=q1.front(); q1.pop(); } else { y=q2.front(); q2.pop(); } ans+=x+y; q2.push(x+y); } write(ans); return 0; }
luogu P1939 矩阵加速(数列)
-
规定 \(a_{0}=0\) 。
-
令 \(F_{n}=\begin{bmatrix} a_{n} & a_{n+1} & a_{n+2} \end{bmatrix}\) ,容易有 \(\begin{aligned} F_{n} &=\begin{bmatrix} a_{n} & a_{n+1} & a_{n+2} \end{bmatrix} \\ &=\begin{bmatrix} a_{n-1} & a_{n} & a_{n+1} \end{bmatrix} \times \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix}\\ &=\begin{bmatrix} a_{n-2} & a_{n-1} & a_{n} \end{bmatrix} \times \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix}^{2} \\ &=\begin{bmatrix} a_{n-3} & a_{n-2} & a_{n-1} \end{bmatrix} \times \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix}^{3} \\ &=\dots \\ &=\begin{bmatrix} a_{0} & a_{1} & a_{2} \end{bmatrix} \times \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix}^{n} \\ &=F_{0} \times \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix}^{n} \end{aligned}\) 。
点击查看代码
struct Matrix { ll ma[5][5]; Matrix() { memset(ma,0,sizeof(ma)); } }f,a; Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p) { Matrix c; for(ll i=1;i<=n;i++) { for(ll j=1;j<=k;j++) { for(ll h=1;h<=m;h++) { c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p; } } } return c; } Matrix qpow(Matrix a,ll b,ll p,ll n) { Matrix ans; for(ll i=1;i<=n;i++) { ans.ma[i][i]=1; } while(b>0) { if(b&1) { ans=mul(ans,a,n,n,n,p); } b>>=1; a=mul(a,a,n,n,n,p); } return ans; } int main() { ll t,b,n=1,m=3,k=3,p=1000000007,i; cin>>t; for(i=1;i<=t;i++) { cin>>b; f.ma[1][1]=0; f.ma[1][2]=f.ma[1][3]=1; a.ma[1][1]=a.ma[1][2]=a.ma[2][2]=a.ma[2][3]=a.ma[3][1]=0; a.ma[1][3]=a.ma[2][1]=a.ma[3][2]=a.ma[3][3]=1; cout<<mul(f,qpow(a,b,p,m),n,m,k,p).ma[1][1]<<endl; } return 0; }
luogu P1349 广义斐波那契数列
-
令 \(F_{n}=\begin{bmatrix} a_{n} & a_{n+1} \end{bmatrix}\) ,容易有 \(\begin{aligned} F_{n} &=\begin{bmatrix} a_{n} & a_{n+1} \end{bmatrix} \\ &=\begin{bmatrix} a_{n-1} & a_{n} \end{bmatrix} \times \begin{bmatrix} 0 & q \\ 1 & p \end{bmatrix} \\ &=\begin{bmatrix} a_{n-2} & a_{n-1} \end{bmatrix} \times \begin{bmatrix} 0 & q \\ 1 & p \end{bmatrix}^{2} \\ &=\begin{bmatrix} a_{n-3} & a_{n-2} \end{bmatrix} \times \begin{bmatrix} 0 & q \\ 1 & p \end{bmatrix}^{3} \\ &=\dots \\ &=\begin{bmatrix} a_{1} & a_{2} \end{bmatrix} \times \begin{bmatrix} 0 & q \\ 1 & p \end{bmatrix}^{n-1} \\ &=F_{1} \times \begin{bmatrix} 0 & q \\ 1 & p \end{bmatrix}^{n-1} \end{aligned}\) 。
点击查看代码
struct Matrix { ll ma[5][5]; Matrix() { memset(ma,0,sizeof(ma)); } }f,a; Matrix mul(Matrix a, Matrix b,ll n,ll m,ll k,ll p) { Matrix c; for(ll i=1;i<=n;i++) { for(ll j=1;j<=k;j++) { for(ll h=1;h<=m;h++) { c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p; } } } return c; } Matrix qpow(Matrix a,ll b,ll p,ll n) { Matrix ans; for(ll i=1;i<=n;i++) { ans.ma[i][i]=1; } while(b>0) { if(b&1) { ans=mul(ans,a,n,n,n,p); } b>>=1; a=mul(a,a,n,n,n,p); } return ans; } int main() { ll b,p,n=1,m=2,k=2; cin>>a.ma[2][2]>>a.ma[1][2]>>f.ma[1][1]>>f.ma[1][2]>>b>>p; a.ma[1][1]=0; a.ma[2][1]=1; cout<<mul(f,qpow(a,b-1,p,m),n,m,k,p).ma[1][1]<<endl; return 0; }
luogu P4910 帕秋莉的手环
-
与 tgHZOJ 264. 选拔队员 不同的是本题是一个环。原首尾的两个珠子会相互影响。考虑对首尾珠子进行分讨。
-
当第一个珠子是金属性珠子时,最后一个珠子不一定是木属性珠子。然后把原来的结论搬过来,不同的数量为 \(\begin{aligned} \sum\limits_{i=0}^{n-1}\dbinom{n-1-i+1}{i} &=Fib_{n+1} \end{aligned}\) 。
-
当第一个珠子是木属性珠子时,第二个珠子和最后一个珠子一定是金属性珠子。然后把原来的结论搬过来,不同的数量为 \(\begin{aligned} \sum\limits_{i=0}^{n-3}\dbinom{n-3-i+1}{i} &=Fib_{n-1} \end{aligned}\) 。
-
故 \(Fib_{n-1}+Fib_{n+1}\) 即为所求。
点击查看代码
struct Matrix { ll ma[5][5]; Matrix() { memset(ma,0,sizeof(ma)); } }f,a; Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p) { Matrix c; for(ll i=1;i<=n;i++) { for(ll j=1;j<=k;j++) { for(ll h=1;h<=m;h++) { c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p; } } } return c; } Matrix qpow(Matrix a,ll b,ll p,ll n) { Matrix ans; for(ll i=1;i<=n;i++) { ans.ma[i][i]=1; } while(b>0) { if(b&1) { ans=mul(ans,a,n,n,n,p); } b>>=1; a=mul(a,a,n,n,n,p); } return ans; } int main() { ll t,b,n=1,m=2,k=2,sum,i,p=1000000007; cin>>t; for(i=1;i<=t;i++) { cin>>b; f.ma[1][1]=0; f.ma[1][2]=1; a.ma[1][1]=0; a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1; sum=mul(f,qpow(a,b-1,p,m),n,m,k,p).ma[1][1]; f.ma[1][1]=0; f.ma[1][2]=1; a.ma[1][1]=0; a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1; cout<<(mul(f,qpow(a,b+1,p,m),n,m,k,p).ma[1][1]+sum)%p<<endl; } return 0; }
每日总结、反思
- 做过的题不能再错。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/17993775,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。