2021年广东工业大学第十五届文远知行杯程序设计竞赛(题解
A M形字符串:
判断回文,字符串是否相同,都可以采用字符串哈希O(1)判断。
题目要计算的是前缀也就是确定了左端点1,所以枚举右端点i。判断1~i (后面这种形式都是表示L-R的字符串子串) 是不是回文,如果是回文,就可以在他下一位置i+1~i+i是不是和1~i相同,如果是那就可以拼成一个M字符串,同样用哈希值判断。那M字符串中两个相同回文重叠的情况呢?只要多判一个位置,也就是i~i+i-1是不是和1~i相同,那样也会拼成一个M字符串。
简单证明一下为什么这样是对的:( 只要判断1~i是否和i~i+i-1以及i+1~i+i相同 )
对于第一种情况(首尾不重复直接拼起来)得到的M字符串长度是2*i,对于第一种情况(首尾重复一个)得到的M字符串长度是2*i-1。
又因为i是从1~n递增的,所以也就判断所有M字符串长度的情况,而且只判断了一次。
代码:
#include<bits/stdc++.h> #define sd(x) scanf("%d",&x) #define lsd(x) scanf("%lld",&x) #define sf(x) scanf("%lf",&x) #define ms(x,y) memset(x,y,sizeof x) #define fu(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define all(a) a.begin(),a.end() #define range(a,x,y) a+x,a+y+x using namespace std; using namespace __gnu_cxx; typedef long long ll; typedef unsigned long long ull; typedef long double ld; typedef pair<ll,ll> P; const int N=2e5+99; const ll INF=1e9+7; const ll base=13331; const ll mod=1e9+7; char s[N]; ll p[N],pre[N],suf[N]; int n; void wo() { p[0]=1; fu(i,1,n) { pre[i]=pre[i-1]*base+s[i]-'a'+1; suf[i]=suf[i-1]*base+s[n-i+1]-'a'+1; p[i]=p[i-1]*base; } } ll g1(int l,int r) {return pre[r]-pre[l-1]*p[r-l+1];} ll g2(int l,int r) {return suf[r]-suf[l-1]*p[r-l+1];} bool check(int i,int j) {return g1(i,j)==g2(n-j+1,n-i+1);} int main() { cin>>(s+1); n=strlen(s+1); wo(); int ans=0; for(int i=1;i<=n;i++) { if(check(1,i))//1~i是回文 { int len=i; if(g1(1,i)==g1(i+1,i+len)) ans++;//判断2个子串相同 if(g1(1,i)==g1(i,i+len-1)) ans++; //printf("%d-%d\n",i,ans); } } cout<<ans<<endl; return 0; }
B 找山坡:
类似单调栈维护矩阵最大面积题目,这里只是多了要在相等的时候更新答案~
代码:
#include<bits/stdc++.h> #define sd(x) scanf("%d",&x) #define lsd(x) scanf("%lld",&x) #define sf(x) scanf("%lf",&x) #define ms(x,y) memset(x,y,sizeof x) #define fu(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define all(a) a.begin(),a.end() #define range(a,x,y) a+x,a+y+x using namespace std; using namespace __gnu_cxx; typedef long long ll; typedef unsigned long long ull; typedef long double ld; typedef pair<ll,ll> P; const int N=1e6+99; const ll INF=1e9+7; const ll base=13331; const ll mod=1e9+7; int st[N],tt,w[N]; int main() { int n;cin>>n; int ans=0; fu(i,1,n) { int x;cin>>x; int tmp=0; while(tt&&st[tt]>x) { tmp+=w[tt]; tt--; } if(!tt) tmp=0;//栈空了,就清空累加 if(tt&&st[tt]==x) w[tt]+=tmp+1,ans=max(ans,w[tt]-1); else st[++tt]=x,w[tt]=tmp+1; } cout<<ans<<endl; return 0; }
C 涂墙:
问任意5个平方数是否能拼成n,可重复用平方数(比赛时看错了,卡了一年),打表感觉比较大的数都可行。比较小的数直接记忆化搜索就行。
#include<bits/stdc++.h> #define sd(x) scanf("%d",&x) #define lsd(x) scanf("%lld",&x) #define sf(x) scanf("%lf",&x) #define ms(x,y) memset(x,y,sizeof x) #define fu(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define all(a) a.begin(),a.end() #define range(a,x,y) a+x,a+y+x using namespace std; using namespace __gnu_cxx; typedef long long ll; typedef unsigned long long ull; typedef long double ld; typedef pair<ll,ll> P; const int N=1e6+99; const ll INF=1e9+7; const ll base=13331; const ll mod=1e9+7; int y[N][10],wan[N]; int p[N]; bool dfs(int n,int c) { if(y[n][c]!=-1) return y[n][c]; if(c==1) return y[n][1]=wan[n]; for(int i=1;i<=1000&&p[i]<=n;i++) { if(dfs(n-p[i],c-1)) return y[n][c]=1; } return y[n][c]=0; } int main() { int t;cin>>t; ms(y,-1); for(int i=1;i<=1000;i++) p[i]=i*i,wan[p[i]]=1; while(t--) { int n;cin>>n; if(n>1000) puts("YES"); else puts(dfs(n,5)?"YES":"NO"); } return 0; }
D 动态序列:
模拟,加个全局数组的偏移量,乘法标记mul,加和标记add。操作1,mul和add都乘b。操作2,add加b。操作5是找到指定位置的数x,答案就是x*mul+add。
操作3和4都是往数组加入数,加入数的话,因为最后输出他的话是按x*mul+add,所以插入的时候应该反着来,(x-add)/mul,这样来消除之前偏移量的影响。
插入数组可以维护2个vector,插入后面,就直接放在当前数组后面,插入前面,可以放入另一个vector后面,到时候反着找。
#include<bits/stdc++.h> #define sd(x) scanf("%d",&x) #define lsd(x) scanf("%lld",&x) #define sf(x) scanf("%lf",&x) #define ms(x,y) memset(x,y,sizeof x) #define fu(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define all(a) a.begin(),a.end() #define range(a,x,y) a+x,a+y+x using namespace std; using namespace __gnu_cxx; typedef long long ll; typedef unsigned long long ull; typedef long double ld; typedef pair<ll,ll> P; const int N=2e5+99; const ll INF=1e9+7; const ll base=13331; const ll mod=1e9+7; vector<ll> a,f; ll mp(ll x,ll n) { ll res=1; while(n) { if(n&1) res=res*x%mod; x=x*x%mod; n>>=1; } return res; } int main() { int n,q;cin>>n>>q; fu(i,1,n) { int x;cin>>x; a.push_back(x); } ll mul=1,add=0,l=0,r=0; while(q--) { ll op,b;cin>>op>>b; if(op==1) mul=mul*b%mod,add=add*b%mod; if(op==2) add=(add+b)%mod; if(op==3) f.push_back((b-add+mod)%mod*mp(mul,mod-2)%mod); if(op==4) a.push_back((b-add+mod)%mod*mp(mul,mod-2)%mod); if(op==5) { ll x; int flen=f.size(); if(flen>=b) x=f[flen-b]; else b-=flen,x=a[b-1]; cout<<(x*mul+add)%mod<<endl; } } return 0; }
E 捡贝壳:
对每个a[i]都分解因数,每个因数x都是一个块,来存放x倍数的下标。对每个块排序,询问l-r里x倍数个数时,直接在x的块里面二分查找l-r包含的下标个数。
#include<bits/stdc++.h> #define sd(x) scanf("%d",&x) #define lsd(x) scanf("%lld",&x) #define sf(x) scanf("%lf",&x) #define ms(x,y) memset(x,y,sizeof x) #define fu(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define all(a) a.begin(),a.end() #define range(a,x,y) a+x,a+y+x using namespace std; using namespace __gnu_cxx; typedef long long ll; typedef unsigned long long ull; typedef long double ld; typedef pair<ll,ll> P; const int N=1e5+99; const ll INF=1e9+7; const ll base=13331; const ll mod=1e9+7; vector<int> pool[N]; int main() { int n,m;cin>>n>>m; fu(i,1,n) { int x;cin>>x; for(int j=1;j<=sqrt(x);j++) { if(x%j==0) { pool[j].push_back(i); if(j*j!=x) pool[x/j].push_back(i); } } } fu(i,1,N-1) sort(pool[i].begin(),pool[i].end()); while(m--) { int l,r,x;cin>>l>>r>>x; cout<<upper_bound(pool[x].begin(),pool[x].end(),r) -lower_bound(pool[x].begin(),pool[x].end(),l)<<endl; } return 0; }
F 钓卡游戏:待补
G 分割:
求面积就是求∑(x[j]-x[i])*∑(y[k]-y[l]),相当于暴力枚举端点。对于这个式子,可以x和y分开求。
比如求∑(x[j]-x[i]),就是在数轴上有1~n个点,求任意2个端点距离的和。很经典的问题,只需考虑相邻两个点之间的边被"用过"多少次,就是该边左右端点数的乘积。
#include<bits/stdc++.h> #define sd(x) scanf("%d",&x) #define lsd(x) scanf("%lld",&x) #define sf(x) scanf("%lf",&x) #define ms(x,y) memset(x,y,sizeof x) #define fu(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define all(a) a.begin(),a.end() #define range(a,x,y) a+x,a+y+x using namespace std; using namespace __gnu_cxx; typedef long long ll; typedef unsigned long long ull; typedef long double ld; typedef pair<ll,ll> P; const int N=2e7+99; const ll INF=1e9+7; const ll base=13331; const ll mod=1e9+7; ll x[N],y[N],sx[N],sy[N]; int main() { int n,m;cin>>n>>m; fu(i,1,n) cin>>x[i]; fu(i,1,m) cin>>y[i]; sort(x+1,x+n+1);sort(y+1,y+m+1); ll ax=0,ay=0; fu(i,1,n-1) { ll now=x[i+1]-x[i]; ax=(ax+now*i%mod*(n-i))%mod; } fu(i,1,m-1) { ll now=y[i+1]-y[i]; ay=(ay+now*i%mod*(m-i))%mod; } cout<<ax*ay%mod<<endl; return 0; }
H 有多短:
官方题解:(偷懒)
- 先说结论:答案是 2*k/(度数为1的节点个数)
- 感性认识:
- 直径肯定是从一个度数1的点到另一个度数1的点
- 所以我们只给所有度数为1相连的边分配权值,其他边全置为0(之后不再考虑)
- 均摊下来,每条边的权值Wi = k/(度数为1的节点个数)
- 所以直径为2*Wi
代码:
#include<bits/stdc++.h> #define sd(x) scanf("%d",&x) #define lsd(x) scanf("%lld",&x) #define sf(x) scanf("%lf",&x) #define ms(x,y) memset(x,y,sizeof x) #define fu(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define all(a) a.begin(),a.end() #define range(a,x,y) a+x,a+y+x using namespace std; using namespace __gnu_cxx; typedef long long ll; typedef unsigned long long ull; typedef long double ld; typedef pair<ll,ll> P; const int N=2e7+99; const ll INF=1e9+7; const ll base=13331; const ll mod=1e9+7; int d[N]; int main() { int n,p=0; double k; cin>>n>>k; fu(i,1,n-1) { int u,v;cin>>u>>v; d[u]++,d[v]++; } fu(i,1,n) if(d[i]==1) p++; printf("%.6f\n",k*2/p); return 0; }
I 母牛哥与子序列:
考虑从一个空序列出发,每加入一个数,对答案贡献是怎么变化,就变成了一道简单的dp题。
#include<bits/stdc++.h> #define sd(x) scanf("%d",&x) #define lsd(x) scanf("%lld",&x) #define sf(x) scanf("%lf",&x) #define ms(x,y) memset(x,y,sizeof x) #define fu(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define all(a) a.begin(),a.end() #define range(a,x,y) a+x,a+y+x using namespace std; using namespace __gnu_cxx; typedef long long ll; typedef unsigned long long ull; typedef long double ld; typedef pair<ll,ll> P; const int N=2e7+99; const ll INF=1e9+7; const ll base=13331; const ll mod=1e9+7; ll dp[N]; int main() { int n;cin>>n; fu(i,1,n) { int x;cin>>x; dp[i]=(dp[i-1]*(1+x)+x)%mod; } cout<<dp[n]<<endl; return 0; }
J 最长连续相同数字的长度:应该是splay,之后补。
浙公网安备 33010602011771号