2024寒假年前集训日记
1.27
闲话
-
下午期末家长会的时候,以为家长还没到,于是就在外面等了
,等不及了发现家长已经进去了,然后班主任也已经开始讲了,乐。 -
吃完晚饭后,搬宿舍到了
。 -
晚上,
关于不要忽略算法的细节的讲话。点击查看讲话
咱这次集训哈后面会安排放视频,学过的没学过的都建议听一下,看自己当时学这个知识点的时候就没有什么细节被忽略了。差分约束大家都学了吧,应该都做了 小K的农场 那题了吧。那题是卡基于 BFS 实现的 SPFA,我就看了眼lrb的代码,他是拿双端队列优化后的 SPFA 过的,其他人通过的我也不知道用什么方式过的。你们有拿 DFS 实现的 SPFA 吗?要是没有的话,你们可以学一下,这题 DFS 实现的 SPFA 跑得飞快。
做题纪要
CF1433E Two Round Dances
CF739A Alyona and mex
1.28
闲话
- 上午放的
视频,但我打了一上午的差分约束,课基本没听。 - 下午放的
视频。 - 晚上,【数据删除】半小时内连续被
两次抓到摸鱼,于是【数据删除】以一己之力让 机房洛谷被禁了。然后 讲了点东西,详见 2024寒假集训纪要 1.28 部分。 - 晚上下课后,因时间不够了,故跟着 @xrlong 和@wkh2008 在小操场跑了一圈,最后和他们差出了
。
做题纪要
luogu P1993 小 K 的农场
- 详见 【学习笔记】差分约束 。
luogu P3275 [SCOI2011] 糖果
- 详见 【学习笔记】差分约束 。
LibreOJ 10033. 「一本通 2.1 例 1」Oulipo
-
板子。点击查看代码
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【模板】字符串哈希
-
板子。点击查看代码
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
-
显然,区间长度具有单调性。考虑二分长度,然后把
值丢到一个map
里面,进行判断。- 容易出错的点:对于一段区间
,只有对于所有的 均有 才满足题意。
- 容易出错的点:对于一段区间
-
需要双哈希。
点击查看代码
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
-
考虑枚举不同的位置,然后将前后的
值再次进行 然后拼起来。 -
因
map
常数过大,在找一个数在区间内出现的次数时,使用sort
挨个枚举进行判断,而不是使用map
存储。 -
这题卡
的模数,故选择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
-
前置知识:若
,则 中最多只有 个不同的数。 -
设
表示 在 中出现的次数, 为 去重后的序列。 即为所求。点击查看代码
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
闲话
- 【数据删除】因昨天晚上的事情被家长接走了,然后机房就变消停了不少。
- 不知道什么时候,
来催了下做题。 - 中午吃完饭后吃了个豆干,然后嗓子就开始疼。
- 下午考勤机(刷脸)到了,不知道是第几次教练给吩咐作息时间。
- 为什么晚上吃饭的时间这么短。
- 晚上吃饭的时候,看了下从机房跑到食堂需要
,应该可以更快。 - 晚上放的
和扩展 视频。视频放完后,出去接水的路上,碰见了 问两个来上课的 都要考期末了为什么还来上课;但平常 就算年级部说停课仍来上课,教练问都不问。震惊到了。 - 晚上下课后,跟着 @xrlong 和@wkh2008 在小操场跑了两圈,勉强跟得上。
做题纪要
luogu P4591 [TJOI2018] 碱基序列
-
令
表示使用第 个氨基酸,能匹配到原碱基串的第 个位置的合法方案数。 -
状态转移方程为
,为方便代码书写,我们选择枚举左右端点 使得 。 -
即为所求。点击查看代码
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」括号配对
-
令
表示为使从 变为合法的括号序列至少需要添加的字符数。 -
定义
。 -
考虑分别对左边或右边进行添加括号以达到合法状态,或者以区间
的形式拆分成两部分求解,故状态转移方程为 。 -
注意初始值的设定。
点击查看代码
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
-
板子。点击查看代码
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
-
有一个比较显然的结论:
的 还是 。 -
因为
表示的是 的最长的 长度,故反着跳 即可。点击查看代码
ll nxt[400002]; char s[400002]; void print(ll x) { if(x>=1) { print(nxt[x]); 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
-
考虑用一个栈维护剩下的字符串,在匹配成功后将匹配成功的部分弹出栈,再继续进行匹配。故需要额外记录
表示 中以 结尾的子串与 的前缀能够匹配的最长长度;特别地,当 时, 为 在 中的某一次出现的位置(左端点)。 -
因为最后需要顺序输出,故使用
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
闲话
- 下午放的
视频。
做题纪要
luogu P3435 [POI2006] OKR-Periods of Words
-
简化题意:给定一个字符串
,求 的所有前缀的最长周期的长度之和。 -
前置知识:对于一个字符串
,由于 是 最长 的长度,故此时有 为 的最小周期长度(最小循环元长度)。- 这里可以画图理解一下。
-
本题因要求最大周期,故需要求最小
长度,需要进行跳 。在跳 的过程中,记得记忆化一下。点击查看代码
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
-
前置知识:当
时,此时有 为 的最大循环次数;否则, 的最大循环次数为 。点击查看代码
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 无线传输
-
当
时,将 复制 次即可;当 时,将 复制 次即可。故 即为所求。点击查看代码
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] 动物园
-
令
表示是 的 数量,故有 。- 这里可以画图理解一下。
-
在查询
时,将 往回跳,直至 从而满足不重叠条件,此时有 。点击查看代码
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【模板】失配树
-
失配树板子。
-
在
的过程中,连一条从 到 的有向边,最后就得到了一棵以 为根节点的树。因 必须是真前缀,故最长公共前缀即失配树上的除自己之外的最近公共祖先。故当 时, 即为所求。- 理解:若
是 的 , 是 的 ,则 是 的 。
点击查看代码
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 逆序对 的过程,同样使用权值树状数组维护排名。在匹配失败或已经有
的子串和 相等时,均减去对序列的影响。 -
注意到可能会有相等的元素,故我们需要同时判断小于等于该数和小于该数是否满足题意。
-
因在本题中判断两个字符串相等的条件是元素排名相等,故
的相等判定也需稍作修改。 -
@Pursuing_OIer 提供了一组
数据。点击查看数据
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]病毒检测
- 非正解
-
因通配符个数不超过
,考虑爆搜。 -
记通配符个数为
,最坏情况下时间复杂度目测为 。点击查看代码
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; }
-
- 正解
-
为方便代码书写,在模式串和文本串最后均添加一个
?
。 -
将模式串按照通配符划分成若干段。设
表示由模式串分出的第 个子串是否能够匹配到文本串第 位。类比爆搜做法,分别对通配符进行分讨,然后进行转移。点击查看代码
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
闲话
上午 安排了一场模拟赛。下午让 @pbbqqd 讲了 , @wkh2008 讲了 。- 问了下 @wkh2008 和 @Pursuing_OIer ,发现他们也嗓子疼,看了眼百度热搜发现这次咽喉炎可能又是传染病。
做题纪要
HZOJ 520. 走迷宫
- 详见 2024初三年前集训测试1 T3 走迷宫 。
luogu P4552 [Poetize6] IncDec Sequence
-
第一问详见 2024初三年前集训测试1 T4 鸭子游戏 。
-
第二问在第一问的基础上,令
。在原来第二步的基础上, 被分配给了 和 进行操作。设 表示此时 进行操作的次数, 表示此时 进行操作的次数,故有 ,其中 。易知 共有 种不同的取值,即最终可得到 种不同的数列。点击查看代码
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]蚯蚓排队
-
观察到
,每次修改最多只会对前后 个位置内的字符串产生影响,故每次连接、分开时使用双向链表进行模拟,进行暴力更改。 -
因要统计每个字符串出现的次数,故将字符串进行
后插入哈希表中进行统计。 -
哈希表的实现因
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
闲话
- 上午放的
视频。 称鉴于初中生到位情况良好,所以决定撤去考勤机,让我们以后自觉到位。然后考勤机就出现在了隔壁高一的机房,乐。
做题纪要
luogu P8306 【模板】字典树
-
树板子。 -
本题选择记录
表示从根节点开始到节点 有多少个模板串以此作为前缀。在搜索 时,搜索完成后直接返回 即可。 -
每组数据前记得手动清空数组。
点击查看代码
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. 前缀统计
-
树板子。 -
本题选择记录
表示节点 是多少个字符串的末尾节点。在搜索 时,沿途累加每个节点的 即可。点击查看代码
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
-
如果在一开始将所有字符串全都插进一棵
树里,最后再寻找要特判找到自己的时候,或者将是否有前缀转化出现次数是否大于 。点击查看代码
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
-
树板子。把 视作一个 位的二进制 串,然后插进一棵 树里。 -
异或性质详见 【学习笔记】数学知识-高斯消元与线性空间 。
-
从贪心角度的分析,我们要尽可能走与当前位相反的字符。
点击查看代码
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
-
带删除板子。点击查看代码
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 让我们异或吧的结论:设
表示从根节点到 的路径上的所有边权的异或值。即 。由异或性质,因两点的 到根的路径异或和会被计算两次从而对答案不产生影响,故 即为所求。点击查看代码
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 和异或
-
考虑把原串分成
和 两段,分别进行统计求和。点击查看代码
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
-
类似马拉车,同样记录最右边的“反对称”字符串右端点。
-
因最终要除去
#
对答案的影响,故 即为所求。点击查看代码
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 神奇项链
-
发现一个回文串可以控制从左端点到右端点的部分,可以理解为区间上的一条线。然后就转化为可重叠区间线段覆盖问题了,贪心或树状数组优化
维护即可。- 贪心策略:以左端点为第一关键字从小到大排序,以右端点为第二关键字从大到小排序。每次选择一个左端点在当前范围内,并且右端点最靠右的线段。
点击查看代码
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
闲话
上午 安排了一场模拟赛。- 题解: 2024初三年前集训测试2
- 感觉
的终端在开了自动保存后貌似有延迟。- 其实是
的自动保存延迟,改了之后就好多了。
- 其实是
终于贯彻了 的作风,明确表示假期集训期间禁止穿校服。- 高一、高二的放假了,机房里有些人跟着一起走了。
做题纪要
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)
-
摩尔投票法板子。
- 摩尔投票法只能找到在序列内出现次数严格大于
的众数。 - 在不保证存在众数的情况下,在摩尔投票法结束后,需要判断下得到的数是否为众数。
- 摩尔投票法只能找到在序列内出现次数严格大于
-
算法的核心思想是用不同候选人之间的票数相互抵消,并记录抵消完此时候选人是否仍然合法,若不合法,则更改下一个。
点击查看代码
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 和异或 ,将原字符串分成两部分,分别进行统计求和。
-
设
表示以 为左端点的最长回文串长度, 表示以 为右端点的最长回文串长度。 -
在马拉车的过程中,找到回文串长度后,对该回文串内进行暴力转移,时间复杂度为
。 -
考虑在马拉车的过程中只对找到的最长回文串的左右端点进行转移。然后限制左右端点只能为
#
,得到状态转移方程 。 -
因我们实际的操作是以
#
为左右端点的,故 即为所求。点击查看代码
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
-
板子。点击查看代码
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]计算器
-
同余半家桶。
-
不保证
,记得特判。点击查看代码
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
闲话
也表示能不穿校服就不穿校服,- 高三的也放假了,中午
没有通知去哪个食堂吃饭,但跟着一大群高一的去了小食堂,总共 个窗口,有点拥挤。看来以后去食堂得加快速度了。 - 又被通知搬宿舍,搬到了
,貌似隔壁就是宿管的宿舍。搬完宿舍后去吃饭,看见了转生奥的 @reach_the_top ,还有我在家那边乡村中学才看到的现象——不顾旁边桌子上还有人在吃饭,直接踩着桌子过去。
做题纪要
luogu P3306 [SDOI2013] 随机数生成器
-
多倍经验: [ABC270G] Sequence in mod P | Gym103486C Random Number Generator
-
递推式为
,发现可以统一消去 ,只在最后参与计算。以下过程省去模运算。 -
当
时,则 即为所求。 -
当
时,递推式转化为 。若 ,则 即为所求;否则无解。 -
当
时,设 。- 当
时,递推式转化为 ,将 代入得 ,移项得 。当 时,移项得 ,解得 ;否则无解。 - 当
时,解得 并和 一起代入原式得 。当 时,移项得 ,跑遍 求解即可;否则无解。
点击查看代码
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
-
钦定
为根节点。 -
第一遍
时,设 表示以 为根的子树内的所有节点到 的距离之和,即 。 -
第二遍
时,进行换根。设 表示以 为整棵树的根节点时,所有节点到 的距离之和,仍钦定 为根节点。考虑根节点由 变为 的过程中,以 为根的子树内的 个节点对答案的贡献会减少 ,以 为根节点的树内除了以 为根的子树内的 个节点对答案的贡献会增加 ,状态转移方程为 。点击查看代码
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
-
发现在将第一个白色变成黑点后,其他将白色变为黑色的顺序并不会影响答案。每次染色,实际得到的贡献为以该节点为根的子树大小。
-
钦定
为根节点。 -
第一遍
时,设 表示先将 变成黑色后,以 为根的子树内的所有节点对 的贡献之和,即 。 -
第二遍
时,进行换根。设 表示以 为整棵树的根节点时,先将 变成黑色后,以 为根的子树内的所有节点对 的贡献之和,仍钦定 为根节点。考虑根节点由 变为 的过程中,以 为根的子树内的 个节点对答案的贡献会减少 ,以 为根节点的树内除了以 为根的子树内的 个节点对答案的贡献会增加 ,状态转移方程为 。点击查看代码
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
-
发现有了点权和边权,但没有太大变化。把点权拆成此处有几个点,计入
计算中。 -
钦定
为根节点。 -
第一遍
时,设 表示以 为根的子树内的所有节点到 的距离之和,即 。 -
第二遍
时,进行换根。设 表示以 为整棵树的根节点时,所有节点到 的距离之和,仍钦定 为根节点。考虑根节点由 变为 的过程中,以 为根的子树内的 个节点对答案的贡献会减少 ,以 为根节点的树内除了以 为根的子树内的 个节点对答案的贡献会增加 ,状态转移方程为 。点击查看代码
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
-
钦定
为根节点。 -
第一遍
时,设 表示以 为根的子树内,当 作为源点,从 出发流向子树的流量的总和的最大值,即 。 -
第二遍
时,进行换根。设 表示以 为整棵树的根节点时,当 作为源点,从 出发流向整个水系的流量的总和的最大值。考虑根节点由 变为 的过程中,原从 流向 的流量为 ,流向除以 为根节点的子树外的其他子树的流量为 ;先从 流向 再流向除以 为根节点的子树外的其他子树的流量为 。状态转移方程为 。点击查看代码
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
-
钦定
为根节点。 -
令
分别表示节点 到以 为根的子树内的叶子节点的最长距离、次长距离和节点 到除以 为根的子树内的叶子节点的最长距离。 -
第一遍
时,处理出 ,并记录到以 为根的子树内的叶子节点的最长距离时的路径上的叶子节点 。 -
第二遍
时,有 。点击查看代码
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
闲话
- 中午因跟着高二的提前
去吃饭,下午就被 了。
做题纪要
luogu P6419 [COCI2014-2015#1] Kamp
-
钦定
为根节点。 -
令
分别表示从节点 出发把以 为根节点的子树内的所有人送回去并回到 的最少时间,节点 到以 为根的子树内的叶子节点的最长距离、次长距离,当 为整棵树的根节点时从节点 出发把以 为根节点的子树内的所有人送回去并回到 的最少时间。 -
第一遍
时,处理出 。具体的,有 。 -
第二遍
时,进行大分讨和进行换根。设 表示以 为整棵树的根节点时,从节点 出发把以 为根节点的子树内的所有人送回去并回到 的最少时间,仍钦定 为根节点。考虑根节点由 变为 的过程中,当 时有 ;当 时有 ;否则,从 出发把除以 为根节点的子树外的所有人送回去并回到 的最少时间为 ,再从 到达 需要再消耗 的时间。故状态转移方程为 。同时记录当 为整棵树的根节点时,节点 到以 为根的子树内的叶子节点的最长距离 。 -
从贪心的角度来看,最长距离我们只走一遍,故
即为所求。点击查看代码
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. 货仓选址
-
简化题意:给定长度为
的序列 ,求 。 -
将
进行排序。设比 小的有 个,比 大的有 个。若 ,则 每增加 , 对答案的贡献会增加 , 对答案的贡献会减少 ,总贡献会减少 ;若 ,则 每减少 , 对答案的贡献会减少 , 对答案的贡献会增加 ,总贡献会减少 。最终当 时取到最小值,即 取 的中位数(非严格意义)时取到最小值。具体的,将 进行排序后,若 为奇数,则 时取到最小值;若 为偶数,有 ,则 时取到最小值。点击查看代码
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 山区建小学
-
将每两个村镇之间的距离
转化成到第一个城镇的距离 。 -
令
表示分别表示 个村镇中建了 个小学的最小距离总和, 个村镇中建了 个小学的最小距离总和。 -
因
此时已经是有序的了,故有 。 -
数组除 外初始化为 。最终 即为所求。点击查看代码
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
-
的处理和普通货仓选址一样。 -
将
排序后,为方便处理,将第 个士兵放在第 个位置。设起点为 ,等价于求 ,然后处理就和普通货仓选址一样了。点击查看代码
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 【模板】矩阵快速幂
-
矩阵快速幂板子。
-
记得定义单位矩阵
。点击查看代码
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
-
令
,容易有 。点击查看代码
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 项和
-
由
,有 。点击查看代码
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
闲话
上午 安排了一场模拟赛,这次把网关了。- 题解: 2024初三年前集训测试3 。
- 不知道为什么
机房又把 Acwing 封了。 - 中午吃饭的时候,又一次看见了 @reach_the_top ,和他搭了几句话。
做题纪要
LibreOJ 10222. 「一本通 6.5 例 4」佳佳的 Fibonacci
-
推式子。
-
点击查看代码
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
-
矩阵快速幂板子。
-
额外知识点
- 主对角线:在
阶行列式中从左上角到右下角的对角线。 - 迹:在
阶行列式中主对角线上元素之和。 - 次对角线:在
阶行列式中从右上角到左下角的对角线。
点击查看代码
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. 选拔队员
-
设有
个女生, 个男生。 -
个男生中,有 个男生需要各放在每两个女生之间,剩余 个男生可以任意放,此时等价于求 的非负整数解数量 。 -
由打表,有
即为所求。- 证明
- 数学归纳法。
- 当
时,有命题成立。 - 假设当
时命题成立。当 时,有 。 - 故命题成立。
点击查看代码
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,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 斐波那契公约数
-
推式子。
-
令
,有 。点击查看代码
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
闲话
- 今天就要
放假回家集训了。 - 上午
称大课间的时候让我们出去活动活动,顺便调侃了我们一下。
做题纪要
luogu P1801 黑匣子
-
查询第
时应维护大根堆的大小始终为 ,此时小根堆的堆顶即为所求。点击查看代码
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 中我们使用堆来维护一堆数中最小的两个数。但本题中优先队列的插入耗时太大,考虑从优化插入入手。容易发现,插入的数具有单调性。
-
将原序列进行排序,并将排序后的数插入一个队列
里;每次合并时,在 和 里面选出最小的两个数,相加统计答案并插入 中。点击查看代码
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 矩阵加速(数列)
-
规定
。 -
令
,容易有 。点击查看代码
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 广义斐波那契数列
-
令
,容易有 。点击查看代码
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. 选拔队员 不同的是本题是一个环。原首尾的两个珠子会相互影响。考虑对首尾珠子进行分讨。
-
当第一个珠子是金属性珠子时,最后一个珠子不一定是木属性珠子。然后把原来的结论搬过来,不同的数量为
。 -
当第一个珠子是木属性珠子时,第二个珠子和最后一个珠子一定是金属性珠子。然后把原来的结论搬过来,不同的数量为
。 -
故
即为所求。点击查看代码
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) 进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具