emmm,看样子比以往的好像难一点,AK的都没有了。。。。不过对于我这种弱鸡好像并没有什么影响
题目链接:https://ac.nowcoder.com/acm/contest/3005#question
题目说明:
A.欧几里得 B.括号序列 C.子段乘积 D.子段异或
(斐波那契) (栈) (前缀乘积) (前缀异或)
E.最小表达式 F.树上博弈 G.音乐鉴赏 H.坐火车
(贪心+大整数) (找规律) (二分) (线段树)
I.匹配星星 J.二维跑步
A.欧几里得
题目大意:给你n(gcd递归的次数),问你gcd中两个数在最小和
示例
输入
1 0
输出
1
说明
gcd(1,0) 由于 b=0,不会递归,即是递归0次。
看起来很唬人,实际上我们打个表就出来了:
for (int i=1; i<=1000; i++) { for (int j=0; j<i; j++) { sum=0; gcd(i,j); if (vis[sum]) continue; vis[sum]=1; printf("%d %d ",i,j); printf("%d ",sum); } }
然后你就可以很清晰地看见$i$和$j$的变化。。。实际上就是个斐波那契数列
以下是AC代码:
#include <bits/stdc++.h> using namespace std; long long f[100]; long long a[100],b[100]; int main(int argc, char const *argv[]) { int t,n; a[0]=1;a[1]=2;b[0]=0;b[1]=1; f[0]=1;f[1]=3; for (int i=2; i<=80; i++){ a[i]=a[i-1]+a[i-2]; b[i]=a[i-1]; f[i]=a[i]+b[i]; } scanf ("%d",&t); while (t--){ scanf ("%d",&n); printf("%lld ",f[n]); } return 0; }
B.括号序列
题目大意:给你一个三种括号(‘()’,‘[]’,‘{}’)的括号序列,问你这个括号序列是否正确
示例
输入
([)]
输出
No
相信一种括号的我们都会,那么多种括号的呢?其实还是一样的做法。我们对左括号入栈,碰到右括号的时候判断一下其他两种括号的栈顶元素的值是否在这种括号栈顶元素的之后,且在该右括号之前,然后将其栈顶出栈,实际上看一下下面的代码就非常清楚了。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e6+10; char s[mac]; int stk1[mac],stk2[mac],stk3[mac]; bool left(char ch) { if (ch=='(' || ch=='[' || ch=='{') return true; return false; } int main(int argc, char const *argv[]) { int n; scanf ("%s",s+1); int len=strlen(s+1); int cnt1=0,cnt2=0,cnt3=0; for (int i=1; i<=len; i++){ if (left(s[i])){ if (s[i]=='(') stk1[++cnt1]=i; else if (s[i]=='[') stk2[++cnt2]=i; else stk3[++cnt3]=i; } else { if (s[i]==')'){ if (cnt1<=0) {printf("No "); return 0;} int pos=stk2[cnt2]; if (pos>stk1[cnt1]) {printf("No "); return 0;} pos=stk3[cnt3]; if (pos>stk1[cnt1]) {printf("No "); return 0;} cnt1--; } else if (s[i]==']'){ if (cnt2<=0) {printf("No "); return 0;} int pos=stk1[cnt1]; if (pos>stk2[cnt2]) {printf("No "); return 0;} pos=stk3[cnt3]; if (pos>stk2[cnt2]) {printf("No "); return 0;} cnt2--; } else { if (cnt3<=0) {printf("No "); return 0;} int pos=stk1[cnt1]; if (pos>stk3[cnt3]) {printf("No "); return 0;} pos=stk2[cnt2]; if (pos>stk3[cnt3]) {printf("No "); return 0;} cnt3--; } } } if (cnt1 || cnt2 || cnt3) printf("No "); else printf("Yes "); return 0; }
C.子段乘积
题目大意:给你一个长度为n的序列,问你长度为k的连续乘对998244353 取模后的最大值
示例
输入
5 3 1 2 3 0 8
输出
6
看着这玩意儿我们很容易想到前缀乘,但我们需要注意的是这里有零,我们只需要稍微处理一下0的情况,我们可以直接将0当成1,但我们需要对0的位置进行维护,我用的是一个简单的队列。然后就是个简单的前缀乘了,如果该区间里面含0位置,那么直接continue掉就好了。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll mod=998244353; const int mac=2e5+10; ll sum[mac]; int zo[mac]; ll qick(ll a,ll b) { ll ans=1; while (b){ if (b&1) ans=(ans*a)%mod; a=a*a%mod; b>>=1; } return ans; } ll ok(int pos,int len) { ll ans=sum[pos+len-1]*qick(sum[pos-1],mod-2)%mod; return ans; } int head=1,tail; bool zero(int pos,int len)//队列维护0的位置 { while (pos>zo[head] && head<=tail) head++; if (head>tail) return false; if (zo[head]<=pos+len-1) return true; return false; } int main(int argc, char const *argv[]) { int n,k,nb=0; scanf ("%d%d",&n,&k); sum[0]=1; for (int i=1; i<=n; i++){ ll x; scanf ("%lld",&x); if (!x) sum[i]=sum[i-1],zo[++nb]=i;//对位置0做些调整 else sum[i]=sum[i-1]*x%mod; } tail=nb; ll ans=0; for (int i=1; i+k-1<=n; i++){ if (zero(i,k)) continue; ll p=ok(i,k); ans=max(ans,p); } printf("%lld ",ans); return 0; }
D.子段异或
题目大意:给你长度为n的序列,问你有几个连续异或值为0的连续序列
示例
输入
5 1 2 3 2 1
输出
2
说明
子段 [1,3] 和子段 [3,5] 是合法子段。
一个连续异或值为0的序列那么假设它的区间为$[l,r]$,那么他的异或值可以用一个前缀来维护:$sum[r]igoplus sum[l-1]$。那么应该区间的异或值为0只有一种情况就是$sum[r]=sum[l-1]$,然后使用map计录每一个数字有多少个前缀和等于那个数字即可。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=2e5+10; typedef long long ll; int sum[mac],oth[mac]; map<int,int>q; ll dp[mac]; int main(int argc, char const *argv[]) { int n; scanf ("%d",&n); for (int i=1; i<=n; i++){ int x; scanf ("%d",&x); sum[i]=sum[i-1]^x; oth[i]=sum[i]; if (!sum[i]) dp[0]++;//注意! } for (int i=1; i<=n; i++){ //printf("%d ",q[sum[i]]); dp[i]=dp[i-1]+q[oth[i]]; q[sum[i]]++; } printf("%lld ",dp[n]); return 0; }
E.最小表达式
题目大意:给你一个字符串只含1-9和加号,问你他们组成的最小的表达式的值是多少
示例
输入
23984692+238752+2+34+
输出
5461
实际上这题就是个简单的贪心,我们先看看需要构筑几个整数,然后依次分配最小值给他们,然后再手写个大整数的加法就没了,代码中有一些关键的注释
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=5e5+10; char s[mac]; int use[mac]; vector<int>g[mac],ans; vector<int> pplus(vector<int>a,vector<int>b) { vector<int>sum; int lena=a.size(),lenb=b.size(); for (int i=1; i<=min(lenb,lena); i++){ sum.push_back(a[lena-i]+b[lenb-i]); } for (int i=min(lena,lenb)+1; i<=lena; i++) sum.push_back(a[lena-i]); for (int i=min(lena,lenb)+1; i<=lenb; i++) sum.push_back(b[lenb-i]); int len=sum.size(); for (int i=0; i<len; i++){ if (sum[i]>=10) { if (i==len-1) sum.push_back(sum[i]/10); else sum[i+1]+=sum[i]/10; sum[i]=sum[i]%10; } } reverse(sum.begin(),sum.end()); return sum; } int main(int argc, char const *argv[]) { scanf ("%s",s+1); int len=strlen(s+1); int cnt=0,p=0; for (int i=1; i<=len; i++){ if (s[i]=='+') p++; else use[++cnt]=s[i]-'0'; } int nb=p+1; sort(use+1,use+1+cnt); int nb_len=cnt/nb; int last=cnt%nb; int pos=1; while (last){ g[pos].push_back(use[pos]);//先给多余的分配 last--; pos++; } int ends=pos+nb-1; for (int i=1; i<=nb; i++){ for (int j=pos; j<=cnt; j+=nb)//这里就是平均分配了,每个整数再分配nb_len个数 g[i].push_back(use[j]); pos++; if (pos>ends) break; } for (int i=1; i<=nb; i++) ans=pplus(ans,g[i]); for (auto x:ans) printf("%d",x); printf(" "); return 0; }
F.树上博弈
题目大意:给你一棵树,牛牛和牛妹在树中的某个节点中,牛牛先走,他们不能走到已经有人的地方,谁不能走了谁就输了,问有多少种不同的初始状态使得牛牛必胜
示例
输入
3 1 2
输出
2
emmm,还是个规律题。当两人的距离为偶数时牛牛必胜,可能一想到树上距离很多人就想到了lca,但实际上这题并不需要,我们只需要判断两点间距的奇偶性就好了,我们先求出每个点到根节点的距离,然后做个奇偶标记,如果$deep[a]$为奇数,$deep[b]$为偶数,那么很容易知道,不管他们中间除了根节点外是否还有公共祖先,都无法改变他们的奇偶性:$dis(a,b)=deep[a]+deep[b]-2*deep[c]$那么一个数减去偶数,它的奇偶性是不会改变的
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e6+10; typedef long long ll; struct node { int to,next; }eg[mac<<1]; int head[mac],num=0,deep[mac]; ll odd,even; void add(int u,int v) { eg[++num]=node{v,head[u]}; head[u]=num; } void dfs(int x,int fa) { for (int i=head[x]; i!=-1; i=eg[i].next){ int v=eg[i].to; if (v==fa) continue; deep[v]=deep[x]+1; dfs(v,x); } } int main(int argc, char const *argv[]) { int n; scanf ("%d",&n); memset(head,-1,sizeof head); for (int i=1; i<n; i++){ int x; scanf ("%d",&x); add(i+1,x);add(x,i+1); } deep[1]=0; dfs(1,-1); for (int i=1; i<=n; i++){ if (deep[i]%2) odd++; else even++; } ll ans=1ll*n*(n-1)-(odd*even*2); printf("%lld ",ans); return 0; }
G.音乐鉴赏
题目大意:
示例
输入
10 99 99 99 99 99 99 99 99 99 99
输出
50.00%
H.坐火车
题目大意:
示例
输入
5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
输出
0 3 4 3 0
I.匹配星星
题目大意:
示例
输入
2 1 1 0 2 2 1
输出
1
J.二维跑步
题目大意:
示例
输入
5 2
输出
5616