隐藏页面特效

The 2024 ICPC Asia East Continent Online Contest (II)

1|0Preface


被徐神带飞咯,全程睡觉看队友卡卡过题,最变态的是 K 我上去乱写了个假做法就下机睡觉了,后面徐神反手就改了个正解出来

这场主要是周五晚上无来由地发烧了,第二天比赛的时候头痛的一批,几乎没法集中精力想代码和写题

但没想到这场最后打的还挺好,开局 1h 不到就把 6 个签过了,然后跟徐神讲了下 C 题意徐神表示秒会

徐神上机写 C 的时候我和祁神把 E 的做法讨论出来了,并得到一个较为简单的实现,此时徐神 C 很快写完交上去 WA 了,在写了对拍后发现欠考虑了些就换祁神上去写 E

本来说好是祁神写我在边上看的,结果看着看着给我看红温了直接头也不痛了,抢键盘冲上去乱写一通交上去就过了

此时发现罚时优势挺大,并且看榜发现这题后面题都不简单,遂决定再 all-in 一个过的人比较多的 K

徐神上机改了下就把 C 调出来了,结果交上去竟然 T 了,在本机搞了一堆强数据测试后感觉做法复杂度没问题后,我直接上去抄了个快读然后发现 54ms 过了

在下面的时候和祁神把 K 题分治+卷积+完全二分图组合计数的思路大致搞了出来,但因为没有想清楚就冲上去写了个会计算重复贡献的做法,最后经典没过样例下机反思

此时我的头痛突然加剧,遂只能趴在桌子上开睡,让祁神把做法跟徐神交流下,后面就在我迷迷糊糊中听队友讨论出一个不重不漏计数的方法,徐神上去也是很快敲出来过了

最后 9 题校排 16 终于打了个像样的排名了,那么根据控制变量法之前究竟是谁在演呢,我不好说


2|0A. Gambling on Choosing Regionals


读懂题意后不难发现所谓的最坏情况就是和强队全撞了,因此最优的决策一定是去队伍数最小的赛站

按能力值从大到小排序后对每个学校开一个桶统计下即可,注意当前队伍所在学校对应的值要减去 1

#include<cstdio> #include<iostream> #include<string> #include<array> #include<algorithm> #include<map> #define RI register int #define CI const int& using namespace std; const int N=100005; int n,k,c[N],C,idx,bkt[N],ans[N]; array <int,3> a[N]; map <string,int> rst; int main() { ios::sync_with_stdio(0); cin.tie(0); cin>>n>>k; C=1e9; for (RI i=1;i<=k;++i) cin>>c[i],C=min(C,c[i]); for (RI i=1;i<=n;++i) { string tmp; cin>>a[i][0]>>tmp; a[i][2]=i; if (rst.count(tmp)) a[i][1]=rst[tmp]; else a[i][1]=rst[tmp]=++idx; } sort(a+1,a+n+1,greater <array <int,3>>()); int sum=0; for (RI i=1;i<=n;++i) { auto [w,sch,id]=a[i]; if (bkt[sch]<C) ++bkt[sch],++sum; ans[id]=(sum-bkt[sch])+(bkt[sch]-1)+1; } for (RI i=1;i<=n;++i) printf("%d\n",ans[i]); return 0; }

3|0B. Mountain Booking


看过题人数是个防 AK,不过这场没过的题好像都是 DS 相关的,看来我的挂机导致我们队没开错题,赢


4|0C. Prefix of Suffixes


string master 专业对口,这么多场网络赛终于有个字符串了

徐神的做法大致就是用 KMP 的 fail 数组等差数列 log 级别的性质来做,因为我一点不懂字符串科技具体的也不懂了

#include <bits/stdc++.h> using llsi = long long signed int; int n; int s[300005], a[300005], b[300005]; int fail[300005], top[300005]; llsi ans = 0, bsum = 0; class FileInputOutput { private: static const int S=1<<21; #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++) char Fin[S],*A,*B; public: template <typename T> inline void read(T& x) { x=0; char ch; while (!isdigit(ch=tc())); while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc())); } #undef tc }F; int main() { F.read(n); for(int i = 1; i <= n; ++i) { F.read(s[i]); F.read(a[i]); F.read(b[i]); s[i] = (s[i] + ans) % n; int j = fail[i - 1]; //int count = 0; if(i == 1) fail[1] = top[1] = 0; else { //count += 1; while(j && s[j + 1] != s[i]) { //count += 1; bsum -= b[i - 1 - j + 1]; j = fail[j]; } if(s[j + 1] == s[i]) fail[i] = j + 1; else fail[i] = 0; if(i - fail[i] == fail[i] - fail[fail[i]]) top[i] = top[fail[i]]; else top[i] = fail[i]; } for(; j > 0; ) { //count += 1; if(s[j + 1] == s[i]) { if(s[fail[j] + 1] == s[i]) j = top[j]; else j = fail[j]; } else { bsum -= b[i - 1 - j + 1]; j = fail[j]; } } // std::cerr << "top[" << i << "] = " << top[i] << char(10); // std::cerr << "fail[" << i << "] = " << fail[i] << char(10); if(s[i] == s[1]) bsum += b[i]; ans += a[i] * bsum; std::cout << ans << char(10); } return 0; }

5|0D. Query on Tree


不可做的 DS 题,鉴定为弃疗


6|0E. Escape


很经典的一个题,想到了就很简单

考虑玩家最后选择的路径上能不能包含点 x,这就要求当人走到这个位置时不能被机器人抓到

首先若所有机器人到该点的最小距离 >d 则该点一定可以走;还有一种情况就是人到的比机器人早

由于有走回头路的情况因此套路地发现这和奇偶性有关,考虑将每个点拆成奇偶两个

对奇偶两种点分别判断人是否能先于机器人到达即可,以此可以确定每个点是否可以被走到,最后对所有合法的点求一个最短路即可

#include<bits/stdc++.h> using namespace std; const int INF = 1e9+5; const int N = 5e5+5; int n, m, d, k, dis1[N], dis2[N], dis3[N], pre[N]; bool valid[N]; vector<int> G[N]; void BFS(queue<int> &Q, int dis[]) { while (!Q.empty()) { int x = Q.front(); Q.pop(); for (int v : G[x]) { if (!valid[v]) continue; if (dis[v] > dis[x]+1) { dis[v] = dis[x]+1; pre[v] = x; Q.push(v); } } } } void solve() { cin >> n >> m >> d; for (int i=1; i<=2*n; ++i) G[i].clear(), dis1[i]=dis2[i]=dis3[i]=INF,valid[i]=1; for (int i=1; i<=m; ++i) { int x, y; cin >> x >> y; int x1=2*x-1, y1=2*y-1; int x2=2*x, y2=2*y; G[x1].push_back(y2); G[y2].push_back(x1); G[x2].push_back(y1); G[y1].push_back(x2); } cin >> k; queue<int> Q; for (int i=1; i<=k; ++i) { int s; cin >> s; dis1[2*s-1] = 0; pre[2*s-1] = -1; Q.push(2*s-1); } BFS(Q, dis1); queue<int> Q2; Q2.push(1); dis2[1] = 0; pre[1] = -1; BFS(Q2, dis2); for (int i=1; i<=2*n; ++i) { if (dis1[i]>d||dis2[i]<dis1[i]) valid[i]=1; else valid[i]=0; } if (!valid[1]) { puts("-1"); return; } queue<int> Q3; Q3.push(1); dis3[1] = 0; pre[1] = -1; BFS(Q3, dis3); if (min(dis3[2*n-1],dis3[2*n])==INF) { puts("-1"); return; } printf("%d\n",min(dis3[2*n-1],dis3[2*n])); int x; if (dis3[2*n-1]<=dis3[2*n]) x=2*n-1; else x=2*n; vector <int> path; while (x!=-1) path.push_back(x),x=pre[x]; reverse(path.begin(),path.end()); for (auto x:path) printf("%d ",(x+1)/2); putchar('\n'); } signed main() { ios::sync_with_stdio(0); cin.tie(0); int t; cin >> t; while (t--) solve(); return 0; }

7|0F. Tourist


纯签到,我题都没看

#include <bits/stdc++.h> int main() { std::ios::sync_with_stdio(false); int n; std::cin >> n; for(int64_t i = 1, rating = 1500, c; i <= n; ++i) { std::cin >> c; rating += c; if(rating >= 4000) { std::cout << i << char(10); return 0; } } std::cout << "-1\n"; return 0; }

8|0G. Game


首先发现平局并没啥用,同时由于赢得的钱不会给胜方因此其实就是个辗转相除的过程,简单模拟一下即可

#include <bits/stdc++.h> using llsi = long long signed int; constexpr llsi mod = 998244353; llsi ksm(llsi a, llsi b) { llsi c = 1; while(b) { if(b & 1) c = c * a % mod; a = a * a % mod; b >>= 1; } return c; } llsi f(llsi x, llsi y, llsi p1, llsi p2) { if(x == 0) return 0; if(y == 0) return 1; return ksm(p1, y / x) * (mod + 1 - f(y % x, x, p2, p1)) % mod; } int main() { std::ios::sync_with_stdio(false); int t; std::cin >> t; while(t--) { llsi x, y, a0, a1, b; std::cin >> x >> y >> a0 >> a1 >> b; llsi t = ksm(a0 + a1, mod - 2); a0 = a0 * t % mod; a1 = a1 * t % mod; std::cout << f(x, y, a0, a1) << char(10); } return 0; }

9|0H. Points Selection


刚开始没仔细看题一直在想“选子集和模 nc”这个限制怎么做,想来想去最优的也就是 bitset 了,遂感觉这是个不可做题,结果最后 20min 才发现题目保证数据随机

由于若 query(a,b,c) 为真,则 query(a,b,c) 也一定为真

因此考虑从小到大枚举 a 的值,并令 fc 表示最小的满足 query(a,b,c) 为真的值 b,有了这个后可以很容易计算答案

每次加入一个点后需要 O(n) 的时间暴力更新 f 数组,但我们可以用随机的性质来做一些分析

考虑加入 k 个点后,随机的性质会使得其 2k 个子集和模 n 的值在 [0,n) 内均匀分布,因此 k=O(logn) 时期望就可以将所有 f 的值填满

因此 f 数组的最大值的期望值等于所有已经加入的点的纵坐标的第 O(logn) 小值,即 O(nlognk)

注意到每次加入一个点时,若它的 y 坐标 max(f) 时可以直接跳过它,因此它更新答案(即其纵坐标 <max(f) )的概率为 O(lognk)

即期望更新次数为 O(k=1nlognk)=O(log2n),总复杂度 O(nlog2n),常数很小可以通过

#include<cstdio> #include<iostream> #include<vector> #include<utility> #define RI register int #define CI const int& using namespace std; typedef pair <int,int> pi; const int N=500005; int n,f[N]; vector <pi> vec[N]; unsigned long long sum,ans; int main() { scanf("%d",&n); for (RI i=1;i<=n;++i) { int x,y,w; scanf("%d%d%d",&x,&y,&w); vec[x].push_back({y,w}); } for (RI i=0;i<n;++i) f[i]=n+1; int maxy=n+1; for (RI x=1;x<=n;++x) { for (auto [y,w]:vec[x]) { if (y>=maxy) continue; static int g[N]; maxy=-1; sum=0; for (RI i=0;i<n;++i) g[i]=f[i]; for (RI i=0;i<n;++i) g[(i+w)%n]=min(g[(i+w)%n],max(f[i],y)); g[w]=min(g[w],y); for (RI i=0;i<n;++i) { f[i]=g[i]; maxy=max(maxy,f[i]); sum+=1ull*i*(1ull*(f[i]+n)*(n-f[i]+1)/2); } } ans+=1ull*x*sum; } return printf("%llu",ans),0; }

10|0I. Strange Binary


祁神开场写的神秘构造,感觉还是挺小清新的

#include<bits/stdc++.h> using namespace std; void solve() { int n; cin >> n; if (n%4==0) { cout << "NO\n"; return ; } vector<int> A(32), ans(32); for (int i=0; i<=30; ++i) { if ((n>>i)&1) A[i] = 1; else A[i] = 0; } auto find1 = [&](int pos) { while (pos>=0) { if (A[pos]==1) return pos; else --pos; } return -1; }; for (int i=31; i>=0; --i) { if (1==A[i]) ans[i]=1; else { int pos = find1(i); if (-1==pos) break; ans[i] = 1; for (int j=i-1; j>=pos; --j) ans[j]=-1; i = pos; } } cout << "YES\n"; for (int i=0; i<32; ++i) { cout << ans[i] << (i%8==7 ? '\n' : ' '); } } signed main() { ios::sync_with_stdio(0); cin.tie(0); int t; cin >> t; while (t--) solve(); return 0; }

11|0J. Stacking of Goods


很套路的题,用交换法可以证明物品 ij 之前当且仅当 ci×wj>cj×wi,改下排序的比较函数即可

#include<cstdio> #include<iostream> #include<algorithm> #define int long long #define RI register int #define CI const int& using namespace std; const int N=100005; struct ifo { int w,v,c; friend inline bool operator < (const ifo& A,const ifo& B) { return A.c*B.w>B.c*A.w; } }a[N]; int n; signed main() { scanf("%lld",&n); for (RI i=1;i<=n;++i) scanf("%lld%lld%lld",&a[i].w,&a[i].v,&a[i].c); sort(a+1,a+n+1); int ans=0,W=0; for (RI i=n;i>=1;--i) ans+=a[i].v-a[i].c*W,W+=a[i].w; return printf("%lld",ans),0; }

12|0K. Match


按位异或的题很容易想到从高位往低位枚举,并令 solve(A,B,p) 表示 A,B 两个集合在高 p 位匹配的答案,返回值为一个多项式

A,B 按当前位的权值为 0/1 分为 A0,A1,B0,B1,考虑以下两种情况:

  • k 的第 p 位为 1,此时只能异或值为 1 的两种方案进行匹配,递归算 solve(A0,B1,p1)solve(A1,B0,p1) 即可;
  • k 的第 p 位为 0,异或值为 1 的两种方案可以任意匹配,先求出 solve(A0,B0,p1)solve(A1,B1,p1) 后剩下的就是个完全二分图匹配,组合 DP 转移即可;

最后总复杂度 O(n4),实际常数极小跑的飞快

#include <bits/stdc++.h> using llsi = long long signed int; constexpr llsi mod = 998244353; using poly = std::vector<llsi>; llsi ksm(llsi a, llsi b) { llsi c = 1; while(b) { if(b & 1) c = c * a % mod; a = a * a % mod; b >>= 1; } return c; } llsi fac[201], facinv[201]; void prep(int n = 200) { fac[0] = 1; for(int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod; facinv[n] = ksm(fac[n], mod - 2); for(int i = n; i >= 1; --i) facinv[i - 1] = facinv[i] * i % mod; return ; } llsi C(llsi a, llsi b) { if(a < b || b < 0) return 0ll; return fac[a] * facinv[b] % mod * facinv[a - b] % mod; } poly mult(const poly &a, const poly &b) { poly c(a.size() + b.size() - 1, 0); for(int i = 0; i < a.size(); ++i) for(int j = 0; j < b.size(); ++j) c[i + j] = (c[i + j] + a[i] * b[j]) % mod; return c; } poly full(int a, int b) { poly c(std::min(a, b) + 1); for(int i = 0; i < c.size(); ++i) c[i] = C(a, i) * C(b, i) % mod * fac[i] % mod; return c; } int n; llsi k; poly solve(const poly &a, const poly &b, int t) { if(a.empty() || b.empty()) return poly {1}; if(t == -1) return full(a.size(), b.size()); poly A[2], B[2]; for(auto a: a) A[a >> t & 1].emplace_back(a); for(auto b: b) B[b >> t & 1].emplace_back(b); if(k >> t & 1) return mult(solve(A[0], B[1], t - 1), solve(A[1], B[0], t - 1)); poly C[2]; C[0] = solve(A[0], B[0], t - 1); C[1] = solve(A[1], B[1], t - 1); poly res(std::min(a.size(), b.size()) + 1, 0); for(int i = 0; i < C[0].size(); ++i) for(int j = 0; j < C[1].size(); ++j) { llsi base = C[0][i] * C[1][j] % mod; llsi a0res = A[0].size() - i, a1res = A[1].size() - j; llsi b0res = B[0].size() - i, b1res = B[1].size() - j; poly aster = mult(full(a0res, b1res), full(a1res, b0res)); for(int k = 0; k < aster.size(); ++k) res[i + j + k] = (res[i + j + k] + base * aster[k]) % mod; } return res; } int main() { std::ios::sync_with_stdio(false); prep(200); std::cin >> n >> k; poly a(n), b(n); for(auto &a: a) std::cin >> a; for(auto &b: b) std::cin >> b; poly ans = solve(a, b, 60); while(ans.size() < n + 1) ans.emplace_back(0); for(int i = 1; i <= n; ++i) std::cout << ans[i] << char(10); return 0; }

13|0L. 502 Bad Gateway


徐神开场写的,做法不难想到就是均值不等式,但需要手写分数类避免精度误差

#include <bits/stdc++.h> using llsi = long long signed int; struct frac { llsi a, b; friend frac operator +(const frac &x, const frac &y) { llsi g = std::__gcd(x.b, y.b); return frac{ x.a * (y.b / g) + y.a * (x.b / g), x.b / g * y.b }; } friend frac operator -(const frac &x, const frac &y) { llsi g = std::__gcd(x.b, y.b); return frac{ x.a * (y.b / g) - y.a * (x.b / g), x.b / g * y.b }; } friend bool operator <(const frac &x, const frac &y) { return x.a * y.b < x.b * y.a; } }; int main() { std::ios::sync_with_stdio(false); int t; std::cin >> t; while(t--) { frac ans { 0x7FFFFFFF, 1 }; llsi T; std::cin >> T; llsi cbase = static_cast<llsi>(std::sqrt(2 * T)); for(llsi c = cbase - 3; c <= cbase + 3; ++c) { if(c <= 0 || c > T) continue; ans = std::min(ans, frac{c * (c + 1) + 2 * T - 2 * c, 2 * c}); } llsi g = std::__gcd(ans.a, ans.b); std::cout << ans.a / g << " " << ans.b / g << char(10); } }

14|0Postscript


网络赛也终于告一段落了,今年区域赛拿了 2+2 的名额,希望能不负众望打出点好成绩吧


__EOF__

本文作者hl666
本文链接https://www.cnblogs.com/cjjsb/p/18427542.html
关于博主:复活的ACM新生,目前爱好仅剩Gal/HBR/雀魂/单机/OSU
版权声明:转载请注明出处
声援博主:欢迎加QQ:2649020702来DD我
posted @   空気力学の詩  阅读(325)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示