隐藏页面特效

The 2021 CCPC Guilin Onsite (XXII Open Cup, Grand Prix of EDG)

1|0Preface


昨天下午16:30~21:30刚打完CCPC2021的广州,今天早上九点又开始打这场桂林,压力拉满了属于是

这场比起昨天那场良心太多了,开场还挺顺(虽然因为写Dijkstra偷懒TLE了四发),但开题啥的都是见一个会一个

中期虽然有点卡但因为祁神会了几何所以没有空机,然后再点完外卖后我突然顿悟把BK过了

然后后面就是对着祁神的F大眼看了两小时,最后上去三倍长了数组就直接过了,看来以后写旋转卡壳都三倍长好了

徐神经典暴肝字符串J,但最后过于繁琐还是没写出来,感觉我真得去加强下字符串了不然后期一点用没有就在摆烂


2|0A. A Hero Named Magnus


签到,输出2x1

#include<cstdio> using namespace std; long long t,x; int main() { //freopen("A.in","r",stdin); freopen("A.out","w",stdout); for (scanf("%lld",&t);t;--t) scanf("%lld",&x),printf("%lld\n",2LL*x-1); return 0; }

3|0B. A Plus B Problem


刚开始执迷于想真正把和维护起来然后一直做不来,后面仔细一想其实完全没必要真的把值算出来

考虑一个位置i的和ci可以写成ci=ai+bi+[aj+bj10],其中j>i且为第一个aj+bj9的位置

因此我们可以想到把所有ai+bi9的数都用一个set维护起来,每次询问要求某个位置的值时就直接查后继即可

然后考虑修改后有多少个数会有影响,首先如果修改后的数和原来相同就直接输出0,否则答案至少为2

考虑什么时候会连带着之前的数产生变化,只有原来要进位,现在不进位/原来不进位,现在要进位两种情况

然后手玩一下就是找到当前位置的前驱,中间所有ai+bi=9的位置都会一起变化,但要注意前驱不存在的情况

总复杂度O(qlogn)

#include<cstdio> #include<iostream> #include<set> #define RI register int #define CI const int& using namespace std; const int N=1e6+5; int n,q,a[N],b[N],r,c,d; char sa[N],sb[N]; set <int> s; inline int getval(CI pos) { auto it=s.upper_bound(pos); if (it==s.end()) return a[pos]+b[pos]; return a[pos]+b[pos]+(a[*it]+b[*it]>=10); } inline int getchg(CI pos) { auto it=s.lower_bound(pos); if (it==s.begin()) return 1+pos; return 2+pos-*(--it); } int main() { //freopen("B.in","r",stdin); freopen("B.out","w",stdout); RI i; for (scanf("%d%d%s%s",&n,&q,sa+1,sb+1),i=1;i<=n;++i) { a[i]=sa[i]-'0'; b[i]=sb[i]-'0'; if (a[i]+b[i]!=9) s.insert(i); } for (i=1;i<=q;++i) { scanf("%d%d%d",&r,&c,&d); int pre=getval(c); if (r==1) { if (a[c]==d) { printf("%d 0\n",pre%10); continue; } if (a[c]+b[c]!=9) s.erase(c); if (a[c]=d,a[c]+b[c]!=9) s.insert(c); } else { if (b[c]==d) { printf("%d 0\n",pre%10); continue; } if (a[c]+b[c]!=9) s.erase(c); if (b[c]=d,a[c]+b[c]!=9) s.insert(c); } int cur=getval(c); if (pre>=10) { if (cur>=10) { printf("%d 2\n",cur%10); continue; } printf("%d %d\n",cur%10,getchg(c)); } else { if (cur<10) { printf("%d 2\n",cur%10); continue; } printf("%d %d\n",cur%10,getchg(c)); } } return 0; }

4|0C. AC Automaton


放AK题,弃!


5|0D. Assumption is All You Need


ORZ祁神秒了构造

考虑从大到小依次把数放到对应的位置,不难发现每个数只能向后移动

采用如下的贪心策略,假设当前数位于x,目标位置为y(y>x),则考虑找到[x+1,y]中最大的数z,将xz上的数交换

这样的正确性很容易感性理解,因为我们在把当前这个数归位的同时,尽量地将后面大的数向前移动以保证它们在后面的操作可以归位,同时交换次数上界也是符合要求的

总复杂度O(n2)

#include<bits/stdc++.h> using namespace std; const int N = 2500; vector<pair<int, int> > ans; int t, n, A[N], B[N], st[N], ed[N]; int mx[N]; void myswap(int x, int y){ ans.emplace_back(x, y); st[A[x]]=y, st[A[y]]=x; swap(A[x], A[y]); } int main(){ scanf("%d", &t); while (t--){ ans.clear(); scanf("%d", &n); for (int i=1; i<=n; ++i) scanf("%d", &A[i]), st[A[i]]=i; for (int i=1; i<=n; ++i) scanf("%d", &B[i]), ed[B[i]]=i; bool ok=true; for (int i=n; i>0; --i){ if (st[i]>ed[i]){ok=false; break;} mx[ed[i]] = A[ed[i]]; for (int j=ed[i]-1; j>=st[i]; --j) mx[j]=max(mx[j+1], A[j]); int cur=st[i]; while (cur<ed[i]){ // printf("cur=%d\n, ed[i]=%d", cur, ed[i]); int nxt = st[mx[cur+1]]; myswap(cur, nxt); cur = nxt; } A[ed[i]]=-1; } if (!ok) printf("-1\n"); else{ printf("%d\n", ans.size()); for (auto [x, y] : ans) printf("%d %d\n", x, y); } } return 0; }

6|0E. Buy and Delete


首先要发现其实Bob的操作次数就是{0,1,2},同时为0的情况可以特判

考虑要让Bob删两轮,就是要使图中有至少一个环,那我们不妨就求出图中权值之和最小的环即可

可以枚举某条边uv,钦定它一定在环上,那么我们直接在剩下的图上跑出vu的最短路,再加上这条边的边权即可

但如果直接这么做的话复杂度是O(m×(n+m)logn)的,有些卡常

仔细一想会发现其实可能的起点只有n个,因此可以优化成O(n×(n+m)logn),即可通过此题

#include<cstdio> #include<iostream> #include<utility> #include<queue> #include<random> #include<algorithm> #define int long long #define RI register int #define CI const int& #define fi first #define se second using namespace std; typedef pair <int,int> pi; const int N=2005,M=5005,INF=1e18; int n,m,c,id[M],tx[M],ty[M],tz[M],x[M],y[M],z[M],dis[N][N],vis[N],ext[N]; vector <pi> v[N]; signed main() { //freopen("E.in","r",stdin); freopen("E.out","w",stdout); RI i,j; int mi=1e9; for (scanf("%lld%lld%lld",&n,&m,&c),i=1;i<=m;++i) scanf("%lld%lld%lld",&tx[i],&ty[i],&tz[i]),id[i]=i,mi=min(mi,tz[i]); if (c<mi) return puts("0"),0; mt19937_64 rng((std::random_device())()); shuffle(id+1,id+m+1,rng); for (i=1;i<=m;++i) x[id[i]]=tx[i],y[id[i]]=ty[i],z[id[i]]=tz[i]; for (i=1;i<=m;++i) { if (!ext[y[i]]) { for (j=1;j<=n;++j) v[j].clear(),dis[y[i]][j]=INF,vis[j]=0; for (j=1;j<=m;++j) if (i!=j) v[x[j]].push_back(pi(y[j],z[j])); priority_queue <pi,vector <pi>,greater <pi>> hp; hp.push(pi(dis[y[i]][y[i]]=0,y[i])); ext[y[i]]=1; while (!hp.empty()) { int now=hp.top().se; hp.pop(); if (vis[now]) continue; vis[now]=1; for (auto [to,w]:v[now]) if (dis[y[i]][to]>dis[y[i]][now]+w) hp.push(pi(dis[y[i]][to]=dis[y[i]][now]+w,to)); } } if (dis[y[i]][x[i]]+z[i]<=c) return puts("2"),0; } return puts("1"),0; }

7|0F. Illuminations II


小清新计算几何题,就连像我这种常年不写几何的人看完题目就知道怎么做了

首先由于凸包的性质不难发现对于内凸包的每条边,它们要么同时被照亮要么都不被照亮

那么我们不妨把这条边所在的直线与外凸包的交点求出来,则这两点之间对应的外凸包边长除以外凸包周长的值就是它被照亮的概率,最后根据期望的线性性累加起来即可

实现的时候可以维护每条边长的前缀和,然后用一个旋转卡壳维护交在外凸包的哪个点即可

复杂度O(n+m)

#include<bits/stdc++.h> #define LD long double #define int long long using namespace std; struct Pt{ int x, y; Pt operator-(const Pt b)const{return Pt{x-b.x, y-b.y};} int crs(Pt b)const{return x*b.y-y*b.x;} LD len()const{return sqrtl(x*x+y*y);} }; int cross(Pt p, Pt a, Pt b){return (a-p).crs(b-p);} using VecP = vector<Pt>; LD ins(Pt p1, Pt v1, Pt p2, Pt v2){ return v1.len() * (1.0L*v2.crs(p1-p2) / v1.crs(v2)); } const int N = 2e5+5; int n, m; VecP tmpa, tmpb, cha, chb; LD len[N*4]; signed main(){ scanf("%lld%lld", &n, &m); tmpa.resize(n); tmpb.resize(m); for (int i=0; i<n; ++i) scanf("%lld%lld", &tmpa[i].x, &tmpa[i].y); for (int i=0; i<m; ++i) scanf("%lld%lld", &tmpb[i].x, &tmpb[i].y); for (int t=0; t<3; ++t){ cha.insert(cha.end(), tmpa.begin(), tmpa.end()); chb.insert(chb.end(), tmpb.begin(), tmpb.end()); } for (int i=1; i<n; ++i) len[i]=len[i+n]=len[i+2*n]=(cha[i]-cha[i-1]).len(); len[n]=len[n*2]=len[n*3]=(cha[0]-cha[n-1]).len(); for (int i=1; i<=n*3; ++i) len[i]+=len[i-1]; LD ans=0.0L; int p=0, q=0; for (int i=0; i<m; ++i){ while (1){ if (cross(chb[i], chb[(i+1)], cha[p])>=0 && cross(chb[i], chb[(i+1)], cha[(p+1)])<0) break; ++p; } while (1){ if (cross(chb[i], chb[(i+1)], cha[q])<=0 && cross(chb[i], chb[(i+1)], cha[(q+1)])>0) break; ++q; } while (q<p) q+=n; LD res=len[q]-len[p]; LD ta=ins(cha[p], (cha[(p+1)]-cha[p]), chb[i], (chb[(i+1)]-chb[i])); LD tb=ins(cha[q], (cha[(q+1)]-cha[q]), chb[i], (chb[(i+1)]-chb[i])); res-=ta; res+=tb; // printf("i=%d p=%d q=%d res=%.3Lf ta=%Lf tb=%Lf\n", i, p, q, res, ta, tb); ans += (chb[(i+1)]-chb[i]).len() * (res / len[n]); } printf("%.11Lf\n", ans); return 0; }

8|0G. Occupy the Cities


挺Easy的一个题,开场和徐神讲了题意就被徐神秒了,本来我还打算去写贪心的说

直接DP,设fi,0/1表示前i个位置,上个1的位置第一步选择向左/向右的最小轮次,转移稍微讨论下即可

#include <bits/stdc++.h> int t, n; char s[1000005]; int dp[1000005][2]; inline int calc(int fr, int to, int first) { if(to == fr + 1) return 0; int len = to - fr - 1; if(first >= len) return 1; len -= first; return ((len + 1) >> 1) + 1; } int main() { // freopen("1.in", "r", stdin); scanf("%d", &t); while(t--) { scanf("%d", &n); scanf("%s", s + 1); int pre = 0; for(int i = 1; i <= n; ++i) if(s[i] == '1') { if(pre == 0) { if(i == 1) dp[i][0] = dp[i][1] = 0; else dp[i][0] = i - 1; dp[i][1] = i; } else { dp[i][0] = std::min( std::max(dp[pre][0], calc(pre, i, 1)), std::max(dp[pre][1], calc(pre, i, 2)) ); dp[i][1] = std::min( std::max(dp[pre][0], calc(pre, i, 0)), std::max(dp[pre][1], calc(pre, i, 1)) ); } pre = i; } if(pre < n) printf("%d\n", std::min( std::max(dp[pre][0], n - pre + 1), std::max(dp[pre][1], n - pre) )); else printf("%d\n", std::min(dp[pre][0], dp[pre][1])); } return 0; }

9|0H. Popcount Words


题目都没看,做不来捏


10|0I. PTSD


签到题,我题目都没看

#include<bits/stdc++.h> #define int long long using namespace std; const int N = 1e6+5; int t, n; char str[N]; signed main(){ scanf("%lld", &t); while (t--){ scanf("%lld%s", &n, str+1); int cnt=0, ans=0; for (int i=n; i>0; --i){ if (cnt>0 && '1'==str[i]) --cnt, ans+=i; else ++cnt; } printf("%lld\n", ans); } return 0; }

11|0J. Suffix Automaton


徐神写的SAM做法好像很复杂,感觉要用后缀树或者后缀数组试试


12|0K. Tax


本来以为是个网络流之类的题,后面仔细一想发现就是个爆搜题

考虑把以1为起点到每个点的最短路求出来,则一条边uv可以走当且仅当disu+1=disv

不妨考虑直接DFS求解,n个点能构成的最大路径条数其实只有O(3n3)级别的,直接一交就过了

#include<cstdio> #include<iostream> #include<queue> #include<utility> #define int long long #define RI register int #define CI const int& using namespace std; typedef pair <int,int> pi; const int N=55,M=N*N,INF=1e18; int n,m,W[M],x,y,z,dis[N],c[M],ans[N]; vector <pi> v[N]; inline void BFS(CI st) { for (RI i=1;i<=n;++i) dis[i]=INF; queue <int> q; q.push(st); dis[st]=0; while (!q.empty()) { int now=q.front(); q.pop(); for (auto [to,w]:v[now]) if (dis[to]>dis[now]+1) dis[to]=dis[now]+1,q.push(to); } } inline void DFS(CI now,CI ret=0) { ans[now]=min(ans[now],ret); for (auto [to,w]:v[now]) if (dis[to]==dis[now]+1) ++c[w],DFS(to,ret+c[w]*W[w]),--c[w]; } signed main() { //freopen("K.in","r",stdin); freopen("K.out","w",stdout); RI i,j; for (scanf("%lld%lld",&n,&m),i=1;i<=m;++i) scanf("%lld",&W[i]); for (i=1;i<=m;++i) scanf("%lld%lld%lld",&x,&y,&z),v[x].push_back(pi(y,z)),v[y].push_back(pi(x,z)); for (i=1;i<=n;++i) ans[i]=INF; for (BFS(1),DFS(1),i=2;i<=n;++i) printf("%lld\n",ans[i]); return 0; }

13|0L. Wiring Engineering


不可做题,弃!


14|0Postscript


这周训练量挺大的说,要被干晕了……


__EOF__

本文作者hl666
本文链接https://www.cnblogs.com/cjjsb/p/17779001.html
关于博主:复活的ACM新生,目前爱好仅剩Gal/HBR/雀魂/单机/OSU
版权声明:转载请注明出处
声援博主:欢迎加QQ:2649020702来DD我
posted @   空気力学の詩  阅读(239)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· Apache Tomcat RCE漏洞复现(CVE-2025-24813)
历史上的今天:
2020-10-21 AtCoder Grand Contest 027
点击右上角即可分享
微信分享提示