暑假集训CSP提高模拟9
暑假集训CSP提高模拟9
组题人: @Delov
\(T1\) P161. 大众点评 \(0pts\)
-
思路来自 1037 - CSP 2021 提高级第一轮 第 5 题 。
-
\(2n\) 次比较是好做的。不难发现在这些比较是有多余的,考虑减少多余比较。
-
将 \(n\) 座拉面馆两两配对,对奇偶进行分讨。若 \(n\) 为奇数,令 \(a_{n+1}=a_{n}\) 。
-
将第 \(i\) 和 \(i+1\) 座拉面馆配对,并要求 \(a_{i}<a_{i+1}\) ,用 \(\left\lfloor \frac{n}{2} \right\rfloor\) 次比较足够了。
-
那么最终答案的最大值一定来自奇数位置上的数,最小值一定来自偶数位置上的数,至多各消耗 \(\left\lceil \frac{n}{2} \right\rceil-1\) 次比较。
点击查看代码
#include<bits/stdc++.h> #include"ramen.h" using namespace std; void Ramen(int n) { vector<int>maxx,minn; if(n%2==0) { for(int i=0;i<=n-1;i+=2) { if(Compare(i+1,i)==-1) { maxx.push_back(i); minn.push_back(i+1); } else { maxx.push_back(i+1); minn.push_back(i); } } } else { for(int i=0;i<=n-2;i+=2) { if(Compare(i+1,i)==-1) { maxx.push_back(i); minn.push_back(i+1); } else { maxx.push_back(i+1); minn.push_back(i); } } maxx.push_back(n-1); minn.push_back(n-1); } int max_pos=maxx[0],min_pos=minn[0]; for(int i=1;i<maxx.size();i++) { if(Compare(maxx[i],max_pos)==1) { max_pos=maxx[i]; } } for(int i=1;i<minn.size();i++) { if(Compare(min_pos,minn[i])==1) { min_pos=minn[i]; } } Answer(min_pos,max_pos); }
\(T2\) P164. 录取查询 \(100pts\)
-
\(s_{l \sim r}\) 是 \(t\) 的子串当且仅当 \(s_{l \sim r}\) 是有序的,且除 \(s_{l},s_{r}\) 的字符外其他字符的出现次数必须等于全局中这些字符的出现次数。
-
两个限制线段树都可以维护,时间复杂度为 \(O(|\sum|n \log n)\) ,其中 \(|\sum|\) 取小写字母字符集 \(26\) 。
点击查看代码
int cnt[30]; char s[100010]; int val(char x) { return x-'a'+1; } struct SMT { struct SegmentTree { int cnt[30],l,r,lcol,rcol,flag; }tree[400010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void pushup(int rt) { for(int i=1;i<=26;i++) { tree[rt].cnt[i]=tree[lson(rt)].cnt[i]+tree[rson(rt)].cnt[i]; } tree[rt].flag=(tree[lson(rt)].flag==1&&tree[rson(rt)].flag==1&&tree[lson(rt)].rcol<=tree[rson(rt)].lcol); tree[rt].lcol=tree[lson(rt)].lcol; tree[rt].rcol=tree[rson(rt)].rcol; } void build(int rt,int l,int r) { tree[rt].l=l; tree[rt].r=r; if(l==r) { tree[rt].lcol=tree[rt].rcol=val(s[l]); tree[rt].flag=1; tree[rt].cnt[val(s[l])]=1; return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void update(int rt,int pos,char s) { if(tree[rt].l==tree[rt].r) { tree[rt].cnt[tree[rt].lcol]=0; tree[rt].lcol=tree[rt].rcol=val(s); tree[rt].cnt[tree[rt].lcol]=1; return; } int mid=(tree[rt].l+tree[rt].r)/2; if(pos<=mid) { update(lson(rt),pos,s); } else { update(rson(rt),pos,s); } pushup(rt); } SegmentTree query(int rt,int x,int y) { if(x<=tree[rt].l&&tree[rt].r<=y) { return tree[rt]; } int mid=(tree[rt].l+tree[rt].r)/2; SegmentTree p,q,ans; if(y<=mid) { return query(lson(rt),x,y); } else { if(x>mid) { return query(rson(rt),x,y); } else { p=query(lson(rt),x,y); q=query(rson(rt),x,y); for(int i=1;i<=26;i++) { ans.cnt[i]=p.cnt[i]+q.cnt[i]; } ans.flag=(p.flag==1&&q.flag==1&&p.rcol<=q.lcol); ans.lcol=p.lcol; ans.rcol=q.rcol; return ans; } } } bool ask(ll l,ll r) { SegmentTree ans=query(1,l,r); if(ans.flag==0) { return false; } else { for(int i=ans.lcol+1;i<=ans.rcol-1;i++) { if(ans.cnt[i]!=cnt[i]) { return false; } } return true; } } }T; int main() { int n,m,pd,l,r,i; char x; cin>>n>>(s+1); for(i=1;i<=n;i++) { cnt[val(s[i])]++; } T.build(1,1,n); cin>>m; for(i=1;i<=m;i++) { cin>>pd; if(pd==1) { cin>>l>>x; cnt[val(s[l])]--; s[l]=x; cnt[val(s[l])]++; T.update(1,l,x); } else { cin>>l>>r; if(T.ask(l,r)==1) { cout<<"Yes"<<endl; } else { cout<<"No"<<endl; } } } return 0; }
\(T3\) P165. 精准打击 \(0pts\)
-
本题根节点的深度定义为 \(0\) ,不知道为啥题目没给这个条件。
-
部分分
- \(10pts\) :若 \(x=\sum\limits_{i=0}^{d}k^{i}\) ,此时不需要删边,否则断一条边即可。
-
正解
- 连通块为树时显然是使删边数量最小的情况。
- 考虑枚举子树断掉和父亲节点的边后的高度 \(dep\) ,然后贪心地从上往下删子树内的边并尽可能多删,缩小问题规模。
- \(dep=D\) 时不需要断掉和父亲节点的边。
点击查看代码
ll num[70],sum[70]; int main() { ll t,d,k,x,ans,cnt,tot,i,j,h; cin>>t; for(h=1;h<=t;h++) { cin>>d>>k>>x; sum[0]=num[0]=1; for(i=1;i<=d;i++) { num[i]=num[i-1]*k; sum[i]=sum[i-1]+num[i]; } ans=sum[d]; for(i=0;i<=d;i++) { if(sum[i]>=x) { cnt=(i!=d); tot=sum[i]-x;//需要删的点数 for(j=i-1;tot>0&&j>=0;j--) { cnt+=tot/sum[j]; tot%=sum[j]; } ans=min(ans,cnt); } } cout<<ans<<endl; } return 0; }
\(T4\) P172. 你画我猜 \(8pts\)
-
部分分
- \(12pts\) :手摸前三个点。
-
正解
- 感觉和 UVA1657 游戏 Game 挺像。
- 发扬人类智慧,有 \(n,m\) 不会很大。考虑直接枚举所有的 \(n,m\) 。
- 每次回答不知道其实是在表明使上次条件成立的决策集合的和/积拆分不唯一,实际是个不断嵌套的过程。
- 考虑递归,递归边界为和/积拆分方案数是否唯一。其中有许多决策是重复的,可以采用记忆化搜索的形式,又因为会有许多无用的空间,故可以使用
map
。 - 最后枚举决策是否是唯一的。
- 需要注意的是可能存在一人知道了但另一人仍不知道的情况。
点击查看代码
unordered_map<int,int>memory_mul[25],memory_sum[25],mul_cnt,sum_cnt; string name; int divide_mul_memory(int x,int s); int divide_sum_memory(int x,int s); vector<pair<int,int> > divide_mul(int x,int s); vector<pair<int,int> > divide_sum(int x,int s); int dfs_mul_memory(int dep,int x,int s); int dfs_sum_memory(int dep,int x,int s); vector<pair<int,int> > dfs_sum(int dep,vector<pair<int,int> > ans,int s); vector<pair<int,int> > dfs_mul(int dep,vector<pair<int,int> > ans,int s); int divide_mul_memory(int x,int s) { if(mul_cnt.find(x)==mul_cnt.end()) { int ans=0; for(int i=s;i<=sqrt(x);i++) { ans+=(x%i==0); } mul_cnt[x]=ans; } return mul_cnt[x]; } int divide_sum_memory(int x,int s) { if(sum_cnt.find(x)==sum_cnt.end()) { int ans=0; for(int i=s;i<=x/2;i++) { ans++; } sum_cnt[x]=ans; } return sum_cnt[x]; } vector<pair<int,int> > divide_mul(int x,int s) { vector<pair<int,int> > ans; for(int i=s;i<=sqrt(x);i++) { if(x%i==0) { ans.push_back(make_pair(i,x/i)); } } return ans; } vector<pair<int,int> > divide_sum(int x,int s) { vector<pair<int,int> > ans; for(int i=s;i<=x/2;i++) { ans.push_back(make_pair(i,x-i)); } return ans; } int dfs_mul_memory(int dep,int x,int s) { if(dep==0) { return divide_mul_memory(x,s); } else { if(memory_mul[dep].find(x)==memory_mul[dep].end()) { vector<pair<int,int> >ans=divide_mul(x,s); int cnt=0; for(int i=0;i<ans.size();i++) { cnt+=(dfs_sum_memory(dep-1,ans[i].first+ans[i].second,s)>1);//决策不唯一 } memory_mul[dep][x]=cnt; } return memory_mul[dep][x]; } } int dfs_sum_memory(int dep,int x,int s) { if(dep==0) { return divide_sum_memory(x,s); } else { if(memory_sum[dep].find(x)==memory_sum[dep].end()) { vector<pair<int,int> >ans=divide_sum(x,s); int cnt=0; for(int i=0;i<ans.size();i++) { cnt+=(dfs_mul_memory(dep-1,ans[i].first*ans[i].second,s)>1);//决策不唯一 } memory_sum[dep][x]=cnt; } return memory_sum[dep][x]; } } vector<pair<int,int> > dfs_mul(int dep,vector<pair<int,int> > ans,int s)//之前一共说过了 dep 次不知道,剩余的决策集合 { if(dep==0) { return ans; } else { vector<pair<int,int> > s_mul; for(int i=0;i<ans.size();i++) { if(dfs_sum_memory(dep-1,ans[i].first+ans[i].second,s)>1)//决策不唯一 { s_mul.push_back(ans[i]); } } return s_mul; } } vector<pair<int,int> > dfs_sum(int dep,vector<pair<int,int> > ans,int s) { if(dep==0) { return ans; } else { vector<pair<int,int> >s_sum; for(int i=0;i<ans.size();i++) { if(dfs_mul_memory(dep-1,ans[i].first*ans[i].second,s)>1) { s_sum.push_back(ans[i]); } } return s_sum; } } bool ask(int m,int n,int s,int t) { vector<pair<int,int> > a=divide_mul(n*m,s); vector<pair<int,int> > b=divide_sum(n+m,s); if(name=="Alice") { for(int i=1;i<=t;i++) { if(i%2==1) { a=dfs_mul(i-1,a,s); if(a.size()<=1)//提前唯一或不存在直接返回 { return 0; } } else { b=dfs_sum(i-1,b,s); if(b.size()<=1) { return 0; } } } if(t%2==0) { a=dfs_mul(t,a,s); if(a.size()==1)//Alice 知道了 { int ans=0; for(int i=0;i<b.size();i++) { ans+=(dfs_mul(t,divide_mul(b[i].first*b[i].second,s),s).size()==1);//唯一才能成立 } return (ans==1);//Bob 也得知道 } else { return 0; } } else { b=dfs_sum(t,b,s); if(b.size()==1) { int ans=0; for(int i=0;i<a.size();i++) { ans+=(dfs_sum(t,divide_sum(a[i].first+a[i].second,s),s).size()==1); } return (ans==1); } else { return 0; } } } else { for(int i=1;i<=t;i++) { if(i%2==1) { b=dfs_sum(i-1,b,s); if(b.size()<=1) { return 0; } } else { a=dfs_mul(i-1,a,s); if(a.size()<=1) { return 0; } } } if(t%2==0) { b=dfs_sum(t,b,s); if(b.size()==1) { int ans=0; for(int i=0;i<a.size();i++) { ans+=(dfs_sum(t,divide_sum(a[i].first+a[i].second,s),s).size()==1); } return (ans==1); } else { return 0; } } else { a=dfs_mul(t,a,s); if(a.size()==1) { int ans=0; for(int i=0;i<b.size();i++) { ans+=(dfs_mul(t,divide_mul(b[i].first*b[i].second,s),s).size()==1); } return (ans==1); } else { return 0; } } } } int main() { int id,s,t,i,j; cin>>id>>s>>name>>t; for(i=2*s;;i++) { for(j=s;i-j>=j;j++) { if(ask(j,i-j,s,t)==1) { cout<<j<<" "<<i-j<<endl; return 0; } } } }
总结
- 赛时历程:将 \(T1,T2,T3,T4\) 溜了一眼, \(T1\) 的结论自己觉得想一会儿就能会了, \(T2,T3\) 不太可做, \(T4\) 有部分分可以打,加上之前打过交互。遂先写 \(T1\) ,和本地的 \(CE\) 搏斗;然后趁着脑子清醒,写了 \(T4\) 的前几个点; \(T2\) 想过支持动态插入、删除平衡树套线段树维护哈希,想过字典树上更改父亲,但都不可做,最终一共想了 \(35 \min\) 就想出来了, \(15 \min\) 码完加调完,大样例一遍过且跑得飞快就直接交了;最后写 \(T3\) 。
- \(T1\)
- 下标从 \(0\) 开始是没想到的,被绕了一会儿。
- 结论以前刷初赛时专门上网查了一下,所以想了一会儿回忆起来了。
- 以为
Compare(X,Y)
中比较的是 \(x,y\) 的大小关系,但实际上是 \(a_{x},a_{y}\) 的大小关系,于是自认为 \(CE\) 是本地的问题,直接交了,挂了 \(100pts\) 。
- \(T3\)
- 没看见 \(10 \%\) 的数据是保证 \(x\) 为一棵满 \(k\) 叉树的点数和,直接当深度 \(=d\) 来做,挂了 \(10pts\) 。
- \(T4\)
- 以为从
Alice
还是从Bob
开始问并不会影响最终结果,导致手摸样例错了一个点。
- 以为从
后记
-
新题较多,且难度不单调递增。初赛知识点普及场,非传统题普及场。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18326986,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。