暑假集训CSP提高模拟5
1.高一上七月上旬日记2.牛客周赛 Round 493.Denso Create Programming Contest 2024(AtCoder Beginner Contest 361)4.NOIP2024模拟15.NOIP2024模拟26.高一上七月中旬日记7.NOIP2024模拟38.CSP提高组模拟19.暑假集训CSP提高模拟110.暑假集训CSP提高模拟211.暑假集训CSP提高模拟312.暑假集训CSP提高模拟413.高一上七月下旬日记14.暑假集训 加赛1
15.暑假集训CSP提高模拟5
16.暑假集训CSP提高模拟617.加赛218.暑假集训CSP提高模拟719.暑假集训CSP提高模拟820.暑假集训CSP提高模拟921.暑假集训CSP提高模拟1022.暑假集训CSP提高模拟1123.暑假集训CSP提高模拟12暑假集训CSP提高模拟5
组题人: @worldvanquisher
P140. 简单的序列(sequence)
-
部分分
-
算法流程
-
在
中找到一个最大的数使得它目前所处的位置和排序后所处的位置不同或目前所处的位置和排序后所处的位置相同但和右边的数相等,将这个数进行一次操作。 -
手摸样例如下
点击查看样例
6: 8 26 5 21 10 8 13 5 21 10 8 13 5 10 10 8 6 5 10 10 8 6 5 5 10 4 6 5 5 10 4 3 5 5 10 4 3 2 5 10 2 3 2 5 10 2 1 2 5 10 1 1 2 5 10 0 1 2 5 10 ____________ 5: 2 8 7 5 2 4 7 5 2 4 3 5 2 2 3 5 1 2 3 5
点击查看代码
struct node { int val,pos; }a[100]; bool cmp(node a,node b) { return (a.val==b.val)?(a.pos<b.pos):(a.val<b.val); } int main() { int t,n,flag,ans,i,j; scanf("%d",&t); for(j=1;j<=t;j++) { scanf("%d",&n); flag=ans=0; memset(a,-0x3f,sizeof(a)); for(i=1;i<=n;i++) { scanf("%d",&a[i].val); a[i].pos=i; flag|=(a[i].val==1&&i>=3); } if(flag==1) { ans=-1; } else { while(flag==0) { flag=1; sort(a+1,a+1+n,cmp); for(i=n;i>=1;i--) { if(a[i].pos!=i||a[i+1].val==a[i].val) { a[i].val/=2; ans++; flag=0; break; } } } } printf("%d\n",ans); } return 0; }
-
-
-
正解
- 观察到
会对 前后的数产生影响。但小的数不会变大,只能让大的数变小。 - 故考虑贪心,从后往前遍历,若不小于后面的数则除以
,特判 。
点击查看代码
int a[100]; int main() { int t,n,ans,i,j; cin>>t; for(j=1;j<=t;j++) { cin>>n; ans=0; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=n-1;i>=1;i--) { while(a[i]>=a[i+1]&&a[i]!=0&&a[i+1]!=0) { a[i]/=2; ans++; } if(a[i]>=a[i+1]) { ans=-1; break; } } cout<<ans<<endl; } return 0; }
- 观察到
P141. 简单的字符串(string)
-
最小操作次数实际上就是缩短了几位。
-
设当前是第
次操作,则 可能的取值仅有 。 -
部分分
-
枚举
依次判断,双指针维护即可,时间复杂度为 。- 随机数据下跑得挺快,但造一堆连续相同字符的字符串就卡掉了。
点击查看代码
char s[10010]; int main() { int n,flag=0,i,j,k,h; cin>>(s+1); n=strlen(s+1); for(i=n;i>=1;i--) { for(h=i;h<=n;h++) { for(j=1;j<=i-1;j++) { flag=0; for(k=j;k<=j+n-i;k++) { if(s[k]==s[h]) { j=k; flag=1; break; } } if(flag==0) { break; } } if(flag==1) { cout<<n-i<<endl; return 0; } } } cout<<n-1<<endl;//长度 <3 的要特殊处理 return 0; }
-
用前缀和优化查找过程,用
bitset
压一下使时间复杂度为 ,其中 取字符集大小 。点击查看代码
int sum[10010]; char s[10010]; bitset<30>vis; int val(char x) { return x-'a'+1; } int main() { int n,flag=0,i,j,k; cin>>(s+1); n=strlen(s+1); for(k=0;k<=n;k++) { vis[val(s[n-k])]=1; for(j='a';j<='z';j++) { if(vis[val(j)]==1) { flag=1; for(i=1;i<=n;i++) { sum[i]=sum[i-1]+(s[i]==j); } for(i=1;i<=n-k;i++) { flag&=(sum[i+k]-sum[i-1]>=1); } if(flag==1) { cout<<k<<endl; return 0; } } } } return 0; }
-
-
正解
- 将
的变化过程视作 不断向左覆盖的过程,时间复杂度为 ,其中 取字符集大小 。
点击查看代码
char s[10010]; int main() { int n,ans=0x3f3f3f3f,len,pos,i,j; scanf("%s",s+1); n=strlen(s+1); for(i='a';i<='z';i++) { len=-0x3f3f3f3f; pos=0; s[n+1]=i; for(j=1;j<=n+1;j++) { if(s[j]==s[n+1]) { len=max(len,j-pos-1);//计算非 i 的最长段 pos=j; } } if(len!=-0x3f3f3f3f) { ans=min(ans,len); } } printf("%d\n",ans); return 0; }
- 将
P142. 简单的博弈(tree)
-
部分分
:随机的三个点和菊花的两个点,瞎口胡结论或rand()
即可。
-
正解
- 设
表示以 为根的子树的 函数值(子节点不能走到的最小状态),状态转移方程为 ,边界为叶子节点的 函数值为零。- 证明
- 设
有 个子节点 ,那么可以将分成 个独立的部分,每个部分包括 和 的一个子节点。 - 问题来到了怎么求只有一个儿子的
函数。当 为叶子节点时只能断掉 这条边,次数有 ;当 不为叶子节点时,可以选择断掉 到达 的状态,也可以选择断掉 子树内的边到达 子树割掉这条边后的 函数值加一,故有 。 - 接着将这
个部分的值异或起来即得到了 。
- 设
- 证明
- 最后由于
定理,若 则先手必胜,若 则后手必胜。
点击查看代码
struct node { int nxt,to; }e[400010]; int head[400010],sg[400010],cnt=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(int x,int fa) { for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); sg[x]^=sg[e[i].to]+1; } } } int main() { int t,n,u,v,i,j; cin>>t; for(j=1;j<=t;j++) { cin>>n; cnt=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); memset(sg,0,sizeof(sg)); for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs(1,0); if(sg[1]==0) { cout<<"joke3579"<<endl; } else { cout<<"gtm1514"<<endl; } } return 0; }
- 设
P143. 困难的图论(graph)
-
将
的路径序列长度 视作在边权为 的情况下 的最短路长度加一,即 。 -
部分分
:暴力建边,每个类别建一个虚点,同一个类别内部的点到虚点的边权为 ,虚点到这些点的边权为 。因为边权只有 和 ,跑 即可。 :观察到 ,此时图大部分是不连通的,那么就完全不需要管没被这 条边覆盖的点,因为这些点的答案可以通过虚点计算。对这 条边和虚点跑最短路。
-
正解
- 考虑对每个类别以虚点为起点,跑最短路并建出最短路
,那么此时 的路径序列要么经过虚点要么不经过虚点,若经过 所属的类别的虚点则虚点的答案可以直接继承给 ,若不过 所属的类别的虚点则多走 步走到虚点。 - 枚举终点,用
bitset
用来记录下前驱节点跑拓扑转移。 - 需要手写
bitset
。 上因为空间限制开到了 ,需要对其进行分块,用unsigned long long
压 位。
点击查看代码
struct node { ll nxt,to,w; }e[200010]; ll head[200010],p[200010],dis[200010],din[200010],dinn[200010],ans[200010],cnt=0; ull pre[200010]; vector<ll>pos[160],E[200010]; void add(int u,int v,int w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void bfs(ll s) { ll x,i; deque<ll>q; memset(dis,0x3f,sizeof(dis)); dis[s]=0; q.push_back(s); while(q.empty()==0) { x=q.front(); q.pop_front(); for(i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]>dis[x]+e[i].w) { dis[e[i].to]=dis[x]+e[i].w; if(e[i].w==1) { q.push_back(e[i].to); } else { q.push_front(e[i].to); } } } } } void build(ll n) { memset(din,0,sizeof(din)); for(ll x=1;x<=n;x++) { E[x].clear(); for(ll i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]==dis[x]+e[i].w) { E[x].push_back(e[i].to); din[e[i].to]++; } } } } void top_sort(ll n) { ll x,i; queue<ll>q; for(ll i=1;i<=n;i++) { dinn[i]=din[i]; if(dinn[i]==0) { q.push(i); } } while(q.empty()==0) { x=q.front(); q.pop(); for(i=0;i<E[x].size();i++) { pre[E[x][i]]|=pre[x]; dinn[E[x][i]]--; if(dinn[E[x][i]]==0) { q.push(E[x][i]); } } } } int main() { ll n,m,k,siz,u,v,w,i,l,r,j; cin>>n>>m>>k; for(i=1;i<=n;i++) { cin>>p[i]; pos[p[i]].push_back(i); add(i,n+p[i],1); add(n+p[i],i,0); } for(i=1;i<=m;i++) { cin>>u>>v; w=1; add(u,v,w); add(v,u,w); } for(i=1;i<=k;i++) { siz=pos[i].size(); bfs(n+i); build(n+k); for(l=0,r=min(l+63,siz-1);l<=siz-1;l=r+1,r=min(l+63,siz-1)) { memset(pre,0,sizeof(pre)); for(j=l;j<=r;j++) { pre[pos[i][j]]|=(1ull<<(j-l));//自己能到自己 } top_sort(n+k); for(j=1;j<=n;j++) { if(dis[j]==0x3f3f3f3f3f3f3f3f) { ans[2*k+1]+=r-l+1; } else { ans[dis[j]+1]+=__builtin_popcountll(pre[j]);//走到虚点,直接继承(最短路长度不够的走这个类别的点到达) ans[dis[j]+1+1]+=r-l+1-__builtin_popcountll(pre[j]);//不走到虚点,多走 1 步到虚点 } } } } ans[1]=0; for(i=1;i<=2*k+1;i++) { cout<<ans[i]/2<<" ";//因为是每一个种类算一遍,所以 (p,q) 算了两遍,需要除以 2 } return 0; }
- 考虑对每个类别以虚点为起点,跑最短路并建出最短路
总结
多写了个无用变量,从 挂到了 。 @worldvanquisher 称是用来普及 函数的。 读假题了,导致没写暴力,挂了 。
后记
-
没标明 不为负数。 -
没标明 大小,没解释操作具体啥意思,数据随机。 -
大样例又错了,中途更换。 -
首尾呼应
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18316859,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】