隐藏页面特效

2024“钉耙编程”中国大学生算法设计超级联赛(3)

1|0Preface


徐神是我们的红太阳,最后 2min 切了一道极难的 string 使得在这场前期爆炸的局面最后没有崩得太难看

这场前期的开题顺序有点问题导致前 5 题出的很慢,中后期开始三人一人写一题,然后经典三开三卡

好在最后我在 WA 了五发后写对拍把 B 过了,徐神又压线过了 string,但比较可惜的是最后给祁神当小黄鸭找到了他 C 题的几个问题但没时间改了遗憾收场


2|0深度自同构


神秘签到题,我题意都不知道

#include <bits/stdc++.h> int f[1000001] = {1}; int main() { std::ios::sync_with_stdio(false); int n; std::cin >> n; for(int i = 1; i <= n; ++i) for(int j = i; j <= n; j += i) f[j] = (f[j] + f[i - 1]) % 998244353; for(int i = 1; i <= n; ++i) std::cout << f[i] << char(i == n ? 10 : 32); return 0; }

3|0旅行


很典的线段树分治/启发式合并优化树上 DP,但有人初值设错了瞪了 1h 最后对拍才发现问题,怎么回事呢

考虑一个 trivial 的 DP 做法,令 fi,j 表示处理了 i 的子树,且上传一条端点类型为 j 的路径时最大的权值和;gi 表示在 i 处合并路径,不上传的最大权值和

转移的时候先默认点 x 的所有儿子 y 都不上传路径,得到 sum=yson(x)gy

对于 fx,c 的更新,显然有 fx,c=sum+maxyson(x)fy,cgy,即选择从一个子树内继承路径,这样就不能获得在该子树处合并的贡献

对于 gx 的更新,就是类似树上背包的找到两个子树的 fy,cgy 合并即可,实现时可以对每种不同的第二位记录最大次大值进行处理

考虑优化该做法,不难发现第二维的端点类型不用显式的设出来,而是可以用 map 存储然后合并子树的时候启发式合并

同时再给每个点维护一个增量标记用来处理加减信息即可,总复杂度 O(nlog2n),当然也可以写 O(nlogn) 的线段树合并

#include<cstdio> #include<iostream> #include<map> #include<vector> #define int long long #define RI register int #define CI const int& using namespace std; const int N=200005,INF=1e18; int t,n,c[N],w[N],x,y,tag[N],g[N],ans; vector <int> v[N]; map <int,int> f[N]; inline void DFS(CI now=1,CI fa=0) { int sum=0; tag[now]=g[now]=0; for (auto to:v[now]) if (to!=fa) DFS(to,now),sum+=g[to]; int ret=sum; auto merge=[&](CI x,CI y) { if (f[x].size()<f[y].size()) swap(f[x],f[y]),swap(tag[x],tag[y]); for (auto [col,val]:f[y]) { if (f[x].count(col)) ret=max(ret,sum+(f[x][col]+tag[x])+(val+tag[y])); if (!f[x].count(col)||(f[x][col]+tag[x])<(val+tag[y])) f[x][col]=val+tag[y]-tag[x]; } f[y].clear(); }; for (auto to:v[now]) if (to!=fa) tag[to]-=g[to],merge(now,to); if (f[now].count(c[now])) { ret=max(ret,sum+(f[now][c[now]]+tag[now])+w[now]); if ((f[now][c[now]]+tag[now])<w[now]) f[now][c[now]]=w[now]-tag[now]; } else f[now][c[now]]=w[now]-tag[now]; tag[now]+=sum; g[now]=ret; ans=max(ans,g[now]); } signed main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); for (scanf("%lld",&t);t;--t) { RI i; for (scanf("%lld",&n),i=1;i<=n;++i) v[i].clear(),f[i].clear(); for (i=1;i<=n;++i) scanf("%lld",&c[i]); for (i=1;i<=n;++i) scanf("%lld",&w[i]); for (i=1;i<n;++i) scanf("%lld%lld",&x,&y),v[x].push_back(y),v[y].push_back(x); ans=-INF; DFS(); printf("%lld\n",ans); //for (i=1;i<=n;++i) printf("%lld%c",g[i]," \n"[i==n]); } return 0; }

4|0游走


神秘分讨题,奖励祁神吃一坨大的

很容易发现除了 pi=1 的信息外,其余的信息已经唯一地确定了酒鬼的移动,只要检查两次目击间的时间能否 cover 距离,以及信息间 pi+qi 的奇偶性相同即可

但现在问题是对于 pi=1 的信息需要特别处理,可以不用保证奇偶性必须一样,分多种情况讨论即可

#include<bits/stdc++.h> using namespace std; void solve(){ map<int, int> ifo; int n, m; cin >> n >> m; bool bad=false; int tnz = 1e9+5; int mov1 = -1; int parity = -1; int odd1=-1, even1=0; for (int i=1; i<=m; ++i){ int opt; cin >> opt; if (0==opt){ int x, t; cin >> x >> t; if (bad) continue; if (ifo.count(t)){ if (ifo[t]!=x) bad=true; continue; } ifo[t] = x; if (-1==parity){ if (1==x){ if (t%2==1) odd1 = max(odd1, t); else even1 = max(even1, t); continue; } parity = (t+x)%2; tnz = t; bool meet=false; for (auto [t1, x1] : ifo){ if (t1==t){ meet=true; } if ((t1+x1)%2!=parity){ if (!meet) mov1 = t1; else{bad=true; break;} } } if (bad) continue; } auto it = ifo.find(t); auto checkback = [&]() -> bool { auto it1 = it; ++it1; if (it1!=ifo.end()){ auto [t1, x1] = *it1; if (abs(t-t1) < abs(x-x1)) return false; } return true; }; auto checkfront = [&]() -> bool { if (t > tnz){ auto it1 = it; --it1; auto [t1, x1] = *it1; if (abs(t-t1) < abs(x-x1)) return false; }else if (t > mov1){ if (t-mov1 < x) return false; }else if (t < mov1) return false; return true; }; if (t<tnz){ if (1==x){ if ((t+x)%2!=parity) mov1 = max(t, mov1); if (mov1 > tnz){ bad=true; continue;} int xnz = ifo[tnz]; int x1=1, t1 = ((t+x)%2==parity ? t : t+1); if (abs(t1-tnz) < abs(x1-xnz)){ bad=true; continue;} }else{ if ((t+x)%2!=parity){ bad=true; continue;} if (!checkback() || !checkfront()){ bad=true; continue;} tnz = t; } }else{ if ((t+x)%2!=parity){ bad=true; continue;} if (!checkback() || !checkfront()){ bad=true; continue;} } }else if (2==opt){ if (bad){cout << "bad\n"; continue;} if (-1==parity) cout << "inf\n"; else{ int xnz = ifo[tnz]; cout << tnz-xnz+1 << '\n'; } }else if (1==opt){ if (bad){cout << "bad\n"; continue;} if (-1==parity) cout << (odd1 > even1 ? even1+1 : odd1+1) << '\n'; else cout << ((mov1+2)%2==parity ? mov1+1 : mov1+2) << '\n'; } } } signed main(){ ios::sync_with_stdio(0); cin.tie(0); int t; cin >> t; while (t--) solve(); return 0; }

5|0游戏


神秘概率题,前面的转化都能想到,然后一转换成随机游走模型就歇逼了,鉴定为弃疗


6|0数论


好像是个神秘复杂度分析题,感觉不太可做,弃疗


7|0字符串


徐神伟大,无需多言,这题我题都没看,一个劲地模徐神就完事了

#include <bits/stdc++.h> constexpr int $n = 1000005; using llsi = long long signed int; class SAM { void dfs(int cur) { loop[cur][0] = fa[cur]; for(int i = 1; i <= 20; ++i) loop[cur][i] = loop[loop[cur][i - 1]][i - 1]; for(auto ch: ch[cur]) dfs(ch); } public: int loop[$n][21]; std::vector<int> ch[$n]; int go[$n][2], fa[$n], len[$n], occ[$n], las, newcnt; llsi dp[$n]; int pos[$n], pre[$n][21]; void init(int n) { las = newcnt = 1; for(int i = 0; i < 2; ++i) go[1][i] = 0; } void build_loop() { for(int i = 1; i <= newcnt; ++i) ch[i].clear(); for(int i = 2; i <= newcnt; ++i) ch[fa[i]].push_back(i); dfs(1); } void calc_occ(const std::string &s) { memset(occ, 0, sizeof(int) * (newcnt + 1)); for(int i = 0, cur = 1; i < s.size(); ++i) assert(cur), occ[cur = go[cur][s[i] - 'a']] ++; auto d = [&](auto self, int cur) -> void { for(auto ch: ch[cur]) if(ch) self(self, ch), occ[cur] += occ[ch]; }; d(d, 1); std::vector<bool> locked(newcnt + 1, false); auto fetch_next = [&](int i) { for(int j = 0; j < 2; ++j) if(go[i][j] && occ[go[i][j]] == occ[i]) return go[i][j]; return 0; }; for(int i = 1; i <= newcnt; ++i) for(int j = 0; j < 2; ++j) { int n; if((n = fetch_next(i))) locked[n] = true; } for(int i = 1; i <= newcnt; ++i) if(!locked[i]) { dp[i] = len[fa[i]]; pos[i] = 0; memset(pre[i], 0, sizeof(pre[i])); for(int cur = i, nxt = fetch_next(cur); nxt; cur = nxt, nxt = fetch_next(cur)) { dp[nxt] = dp[cur] + len[fa[nxt]]; pos[nxt] = pos[cur] + 1; pre[nxt][0] = cur; for(int i = 1; i <= 20; ++i) pre[nxt][i] = pre[pre[nxt][i - 1]][i - 1]; } } return ; } int pop(int start, int l) { for(int i = 20; ~i; --i) if(len[loop[start][i]] >= l) start = loop[start][i]; return start; } std::pair<int, int> pop_range(int start, int i) { int res = pop(start, i); return {len[res], len[res] + 1}; } void insert(char a) { int c = a - 'a'; int p = las; int np = las = ++newcnt; len[np] = len[p] + 1; for(int i = 0; i < 2; ++i) go[np][i] = 0; for(; p && !go[p][c]; p = fa[p]) go[p][c] = np; if(!p) { fa[np] = 1; return ; } int q = go[p][c]; if(len[q] == len[p] + 1) { fa[np] = q; return ; } int nq = ++newcnt; len[nq] = len[p] + 1; for(int i = 0; i < 2; ++i) go[nq][i] = go[q][i]; fa[nq] = fa[q]; fa[np] = fa[q] = nq; for(; p && go[p][c] == q; p = fa[p]) go[p][c] = nq; return ; } } S1, S2; int main() { std::ios::sync_with_stdio(false); int t; std::cin >> t; while(t--) { std::string s; std::cin >> s; int n = s.size(); S1.init(n), S2.init(n); for(int i = 0; i < n; ++i) S1.insert(s[i]); for(int i = n - 1; i >= 0; --i) S2.insert(s[i]); std::vector<int> e1(n), e2(n); for(int i = 0, cur = 1; i < n; ++i) e1[i] = cur = S1.go[cur][s[i] - 'a']; for(int i = n - 1, cur = 1; i >= 0; --i) e2[i] = cur = S2.go[cur][s[i] - 'a']; S1.build_loop(), S2.build_loop(); S1.calc_occ(s); llsi lastans = 0; int q; std::cin >> q; while(q--) { llsi op, l, r; std::cin >> op >> l >> r; l = (l + lastans - 1) % n; r = (r + lastans - 1) % n; // std::cerr << "l, r = " << l << ", " << r << char(10); llsi L = r - l + 1; if(op == 1) { auto [l1, l2] = S1.pop_range(e1[r], L); auto [r1, r2] = S2.pop_range(e2[l], L); lastans = (l1 - L + 1) * (r1 - L + 1); } else { int hkr = S1.pop(e1[r], L); // std::cerr << "dp = " << S1.dp[hkr] << ", pos = " << S1.pos[hkr] << ", occ = " << S1.occ[hkr] << char(10); auto pre = S1.pre; auto fa = S1.fa; auto len = S1.len; int d = L, cur = hkr, offset = 0; for(int i = 20; ~i; --i) { int nxt = pre[cur][i]; if(nxt == 0) continue; if(len[fa[nxt]] >= d - (1 << i)) continue; d -= (1 << i); offset |= 1 << i; cur = pre[cur][i]; } // std::cerr << "dp[" << cur << "] = " << S1.dp[cur] << char(10); lastans = L * (offset + 1) - (S1.dp[hkr] - S1.dp[pre[cur][0]]) - (llsi(offset + 1) * (offset) / 2); } std::cout << lastans << char(10); } } return 0; }

8|0单峰数列


把原数组上的信息转化到差分数组上,不难发现每个条件的检验都很 trivial 了

判断单峰时可以线段树上二分找到第一个差分值 0 的位置,然后检验后面一段是否单减即可

#include<cstdio> #include<iostream> #define int long long #define RI register int #define CI const int& using namespace std; const int N=100005,INF=1e18; int n,a[N],d[N],q,opt,l,r,x; class Segment_Tree { private: int mx[N<<2],mn[N<<2]; inline void pushup(CI now) { mx[now]=max(mx[now<<1],mx[now<<1|1]); mn[now]=min(mn[now<<1],mn[now<<1|1]); } public: #define TN CI now=1,CI l=1,CI r=n+1 #define LS now<<1,l,mid #define RS now<<1|1,mid+1,r inline void build(TN) { if (l==r) return (void)(mx[now]=mn[now]=d[l]); int mid=l+r>>1; build(LS); build(RS); pushup(now); } inline void update(CI pos,CI mv,TN) { if (l==r) return (void)(mx[now]+=mv,mn[now]+=mv); int mid=l+r>>1; if (pos<=mid) update(pos,mv,LS); else update(pos,mv,RS); pushup(now); } inline int query_min(CI beg,CI end,TN) { if (beg<=l&&r<=end) return mn[now]; int mid=l+r>>1,ret=INF; if (beg<=mid) ret=min(ret,query_min(beg,end,LS)); if (end>mid) ret=min(ret,query_min(beg,end,RS)); return ret; } inline int query_max(CI beg,CI end,TN) { if (beg<=l&&r<=end) return mx[now]; int mid=l+r>>1,ret=-INF; if (beg<=mid) ret=max(ret,query_max(beg,end,LS)); if (end>mid) ret=max(ret,query_max(beg,end,RS)); return ret; } inline int query_pos(CI beg,CI end,TN) { if (r<beg||l>end) return -1; if (mn[now]>0) return -1; if (l==r) return l; int mid=l+r>>1,res=query_pos(beg,end,LS); if (res!=-1) return res; return query_pos(beg,end,RS); } #undef TN #undef LS #undef RS }SEG; signed main() { RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&a[i]); for (i=1;i<=n+1;++i) d[i]=a[i]-a[i-1]; for (SEG.build(),scanf("%lld",&q);q;--q) { scanf("%lld%lld%lld",&opt,&l,&r); int pos; switch (opt) { case 1: scanf("%lld",&x); SEG.update(l,x); SEG.update(r+1,-x); break; case 2: if (l==r) puts("1"); else puts(SEG.query_min(l+1,r)==0&&SEG.query_max(l+1,r)==0?"1":"0"); break; case 3: if (l==r) puts("1"); else puts(SEG.query_min(l+1,r)>0?"1":"0"); break; case 4: if (l==r) puts("1"); else puts(SEG.query_max(l+1,r)<0?"1":"0"); break; case 5: pos=SEG.query_pos(l+1,r); if (pos==-1||pos==l+1) puts("0"); else puts(SEG.query_max(pos,r)<0?"1":"0"); break; } } return 0; }

9|0比特跳跃


徐神写的神秘优化建图题,我题意都不知道

#include <bits/stdc++.h> using llsi = long long signed int; void work() { int n, m; llsi k; std::cin >> n >> m >> k; int nn = n; for(int i = 0; i <= 20; ++i) n |= n >> i; std::vector<std::vector<std::pair<int, llsi>>> out(2 * n + 1); for(int i = 1, u, v, t; i <= m; ++i) { std::cin >> u >> v >> t; out[u].emplace_back(v, t); out[v].emplace_back(u, t); } for(int i = 1; i <= n; ++i) { out[i].emplace_back(i + n, i * k); out[i + n].emplace_back(i, 0); for(int j = 0; j < 20; ++j) { int nxt = i ^ (1 << j); if(nxt < i) { out[i + n].emplace_back(nxt + n, 0); } else if(nxt <= n) { out[i + n].emplace_back(nxt + n, k << j); } } } std::vector<llsi> dis(2 * n + 1, 0x3f3f3f3f3f3f3f3fll); std::vector<bool> vis(2 * n + 1, false); std::priority_queue<std::pair<llsi, int>> pq; pq.push({0, 1}), dis[1] = 0; while(!pq.empty()) { auto [_dis, cur] = pq.top(); pq.pop(); if(vis[cur]) continue; vis[cur] = true; for(auto [out, len]: out[cur]) if(dis[cur] + len < dis[out]) { dis[out] = dis[cur] + len; pq.push({-dis[out], out}); } } for(int i = 2; i <= nn; ++i) std::cout << dis[i] << char(i == n ? 10 : 32); } int main() { std::ios::sync_with_stdio(false); int t; std::cin >> t; while(t--) work(); return 0; }

10|0圣芙蕾雅


不可做题,弃疗


11|0绘世之卷


开场看的就是这两个中二感爆棚的题,结果都是不可做题


12|0抓拍


挺有意思的一个题

考虑把四个边界关于时间 t 的式子找出来,以 x 的最小值为例,我们先找出往西走的人的横坐标的最小值

如果存在横坐标更小的往其它方向走的人,那么 x 的最小值会在某个时刻变更表达式,因此可以得到一个分段函数

由于题目要求的是周长,所以任何时刻每条边长都是关于时间 t 的一次函数,因此找到分段函数所有分界点代回去暴力检验即可

#include<cstdio> #include<iostream> #include<set> #define int long long #define RI register int #define CI const int& using namespace std; const int N=200005,INF=1e18; const int dx[4]={1,-1,0,0},dy[4]={0,0,-1,1}; int n,x[N],y[N],mnx[4],mxx[4],mny[4],mxy[4]; char s[10],ch[N]; signed main() { RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld%lld%s",&x[i],&y[i],s),ch[i]=s[0]; auto trs=[&](const char& ch) { switch (ch) { case 'E': return 0; case 'W': return 1; case 'S': return 2; case 'N': return 3; default: return -1; } }; for (i=0;i<4;++i) mnx[i]=mny[i]=INF,mxx[i]=mxy[i]=-INF; for (i=1;i<=n;++i) { int tp=trs(ch[i]); mnx[tp]=min(mnx[tp],x[i]); mxx[tp]=max(mxx[tp],x[i]); mny[tp]=min(mny[tp],y[i]); mxy[tp]=max(mxy[tp],y[i]); } set <int> key; key.insert(0); if (mxx[0]!=-INF) { int mx=max(mxx[2],mxx[3]); if (mxx[0]<mx) key.insert(mx-mxx[0]); mx=mxx[1]; if (mxx[0]<mx) key.insert((mx-mxx[0])/2),key.insert((mx-mxx[0]+1)/2); } if (mnx[1]!=INF) { int mn=min(mnx[2],mnx[3]); if (mnx[1]>mn) key.insert(mnx[1]-mn); mn=mnx[0]; if (mnx[1]>mn) key.insert((mnx[1]-mn)/2),key.insert((mnx[1]-mn+1)/2); } if (mny[2]!=INF) { int mn=min(mny[0],mny[1]); if (mny[2]>mn) key.insert(mny[2]-mn); mn=mny[3]; if (mny[2]>mn) key.insert((mny[2]-mn)/2),key.insert((mny[2]-mn+1)/2); } if (mxy[3]!=INF) { int mx=max(mxy[0],mxy[1]); if (mxy[3]<mx) key.insert(mx-mxy[3]); mx=mxy[2]; if (mxy[3]<mx) key.insert((mx-mxy[3])/2),key.insert((mx-mxy[3]+1)/2); } int ans=INF; for (auto t:key) { int res=0,xl=INF,xr=-INF,yl=INF,yr=-INF; for (i=1;i<=n;++i) { int tp=trs(ch[i]),nx=x[i]+t*dx[tp],ny=y[i]+t*dy[tp]; xl=min(xl,nx); xr=max(xr,nx); yl=min(yl,ny); yr=max(yr,ny); } ans=min(ans,(xr-xl)+(yr-yl)); } return printf("%lld",2LL*ans),0; }

13|0死亡之组


签到题,简单分类讨论即可

#include<bits/stdc++.h> using namespace std; const int N = 1e5+5; int t, n, L, D, A[N], id[N]; signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin >> t; while (t--){ cin >> n >> L >> D; for (int i=1; i<=n; ++i) cin >> A[i], id[i]=i; sort(id+2, id+n+1, [&](int a, int b){return A[a]<A[b];}); if (A[1]>=L){ if (A[id[4]]<L && A[1]-A[id[2]]>D){ cout << "Yes\n"; }else cout << "No\n"; }else{ if (A[id[3]]<L && A[id[n]]-min({A[id[1]], A[id[2]], A[id[3]]})>D){ cout << "Yes\n"; }else cout << "No\n"; } } return 0; }

14|0Postscript


一周四场多校还是有点爽歪歪的说


__EOF__

本文作者hl666
本文链接https://www.cnblogs.com/cjjsb/p/18326139.html
关于博主:复活的ACM新生,目前爱好仅剩Gal/HBR/雀魂/单机/OSU
版权声明:转载请注明出处
声援博主:欢迎加QQ:2649020702来DD我
posted @   空気力学の詩  阅读(1052)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
历史上的今天:
2020-07-26 Luogu P5445 [APIO2019] 路灯
2018-07-26 Luogu P3177 [HAOI2015]树上染色
2018-07-26 51Nod 1677 treecnt
点击右上角即可分享
微信分享提示