The 2024 ICPC Asia EC Regionals Online Contest (II)
写在前面
补题地址:https://codeforces.com/gym/105358。
以下按个人向难度排序。
妈的 7 题秒完剩下的题感觉没一个能做的。
F 签到
wenqizhi 大神秒了,我看都没看。
Code by wenqizhi:
复制复制#include <bits/stdc++.h> #define ll long long const int N = 1e5 + 5; ll a[N], n; int main() { scanf("%d", &n); ll ans = 1500; for(int i = 1; i <= n; ++i) { scanf(" %d", &a[i]); ans += a[i]; if(ans >= 4000) { printf("%d\n", i); return 0; } } printf("-1\n"); return 0; }
A 枚举
显然每个队一定会选学校限制报名队伍数量最少的站,若该站的上限为 ,则仅需考虑每个学校前 强的队伍即可。
然后将这些队伍排序,再枚举所有队伍检查他们单独插入后的排名即可,可使用 set
简单维护。
#include <bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long #define pr pair #define mp make_pair int read() { int x = 0; bool f = false; char c = getchar(); while(c < '0' || c > '9') f |= (c == '-'), c = getchar(); while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return f ? -x : x; } const int kInf = 1e9 + 10; const int kN = 1e5 + 10; int num, n, k; int w[kN], schoolid[kN]; vector<pr <int, int> > team[kN]; map <string, int> id; set <pr <int, int> > endteam; int ans[kN]; int main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n >> k; int minc = kInf; for (int i = 1; i <= k; ++ i) { int c; cin >> c; minc = min(minc, c); } for (int i = 1; i <= n; ++i) { string school; int x; cin >> x >> school; if (!id.count(school)) id[school] = ++ num; team[id[school]].push_back(mp(-x, i)); w[i] = x; schoolid[i] = id[school]; } for (int i = 1; i <= num; ++ i) { sort(team[i].begin(), team[i].end()); while (team[i].size() > minc) team[i].pop_back(); for (auto x: team[i]) endteam.insert(x); } int cnt = 0; endteam.insert(mp(-1, n + 1)); for (auto x: endteam) { // cout << x.first << " " << x.second << "\n"; ans[x.second] = ++ cnt; } for (int i = 1; i <= n; ++ i) { if (ans[i]) continue; auto it = endteam.lower_bound(mp(-w[i], 0)); ans[i] = ans[it->second] - 1; } for (int i = 1; i <= n; ++ i) cout << ans[i] << "\n"; return 0; }
J 贪心
保证了 ,则实际上不需要考虑 的值,仅需最大化 即可。
发现相邻两个位置交换后仅影响这两个位置的 ,对前后缀每个位置的贡献无影响,一眼国王游戏,考虑微扰法发现仅需按照 升序排序即可。
code by wenqizhi:
#include <bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long ll read() { ll x = 0; bool f = false; char c = getchar(); while(c < '0' || c > '9') f |= (c == '-'), c = getchar(); while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return f ? -x : x; } const int N = 1e5 + 5; int n; struct node { ll w, v, c; node(){ w = v = c = 0; } bool friend operator < (const node &a, const node &b) { return a.c * b.w < b.c * a.w ; } }A[N]; int main() { // ios::sync_with_stdio(0); // cin.tie(0), cout.tie(0); n = read(); ll V = 0, W = 0; for(int i = 1; i <= n; ++i) { A[i].w = read(), A[i].v = read(), A[i].c = read(); V += A[i].v ; } sort(A + 1, A + n + 1); for(int i = 1; i <= n; ++i) V -= W * A[i].c , W += A[i].w ; printf("%lld\n", V); return 0; }
I 构造,二进制
发现对于第 位上 1,可以将其调整为 的形式,即变为二进制位上的一个 1 一个 -1,则可将第 位上的 0 消去。
然后发现不断通过上述策略调整即可仅使用 1 和 -1 对原有的二进制位进行调整,消去原有的 0 进行构造。若无法构造说明无解,且发现此时一定是 4 的倍数,使得无论怎么构造最后两位的 0 都无法消去。
code by dztlb:
#include <bits/stdc++.h> using namespace std; #define int long long const int N=1e6+5; #define ll long long #define ull unsigned long long int read() { int x = 0; bool f = false; char c = getchar(); while(c < '0' || c > '9') f |= (c == '-'), c = getchar(); while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return f ? -x : x; } int n,T; int a[33]; signed main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); // n=read(); std::cin>>T; while(T--){ cin>>n; for(int i=0;i<=31;++i){ if((n>>i)&1) a[i]=1; else a[i]=0; } for(int i=0;i<31;++i){ if(a[i]==1&&a[i+1]==0) a[i+1]=1,a[i]=-1; } bool fl=0; for(int i=0;i<31;++i){ if(a[i]==0&&a[i+1]==0) fl=1; } if(fl) cout<<"NO\n"; else{ cout<<"YES\n"; // cout<<a[31]<<endl; for(int i=1;i<=4;++i){ for(int j=0;j<8;++j){ cout<<a[(i-1)*8+j]<<' '; } cout<<'\n'; } } } return 0; }
L 数学,三分
dztlb 大神秒了我看都没看,上去给大神写了个整数三分就过了。
显然两种操作不会交替使用,因为先进行操作 1 后进行操作 2,发现本次操作 1 实际上是没有贡献的。则一定是先不断进行操作 2 直至某个阈值 ,再不断进行操作 1 直至 0。
于是考虑枚举进行若干次操作 2 后的阈值 ,求得第一次操作使得小于该阈值的期望步数。一次操作的概率为 ,发现这是个典型的 的帕斯卡分布,仅需考虑不小于 次操作后使大于阈值的概率,考虑级数求和可知期望步数即:
然后考虑操作 2 使得小于阈值后的取值在 中概率均等,则进行操作 1 的期望次数显然即:
则对于某个阈值 的期望操作次数即为:
显然是个对勾函数的形式,可以直接解出或三分求得极值点。
code by dztlb:
#include <bits/stdc++.h> using namespace std; #define int long long const int N=1e6+5; #define ll long long #define ull unsigned long long int read() { int x = 0; bool f = false; char c = getchar(); while(c < '0' || c > '9') f |= (c == '-'), c = getchar(); while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return f ? -x : x; } int n; int t; double check(int k){ return (double)(k-1.00)/2+(double)t/k; } signed main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); // n=read(); std::cin>>n; for(int i=1;i<=n;++i){ cin>>t; // for(int j=1;j<=t;++j){ // cout<<j<<' '<<check(j)<<'\n'; // } int lmid, rmid; int id=1; double lans, rans,ans=check(1); for (int l = 1, r = t; l <= r; ) { lmid = l + (r - l + 1) / 3; rmid = r - (r - l + 1) / 3; lans = check(lmid); rans = check(rmid); if(ans>lans) id=lmid,ans=lans; if(ans>rans) id=rmid,ans=rans; // cout<<lans<<' '<<rans<<' '<<l<<' '<<r<<'\n'; if (lans <= rans) r = rmid - 1; else l = lmid + 1; } for(int j=min(lmid,rmid)-5;j<=max(lmid,rmid)+5;++j){ if(j<1||j>t) continue; lans = check(j); if(ans>lans) id=j,ans=lans; } // id=13; int x=id*(id-1)+2*t; int y=2*id; int gg=__gcd(x,y); x/=gg,y/=gg; cout<<x<<' '<<y<<'\n'; } return 0; }
G 数学,辗转相除
发现薯薯的总数单调递减,于是想到能否递归地计算。
显然仅需分别考虑 与 的情况,然后可以分别写出两种情况的递推公式。发现每次递归均会使 或 是一个辗转相减的形式,且都可以转化为等比数列的形式,于是考虑将同种的连续的辗转相减合并为辗转相除,然后套用等比数列公式即可。
code by wenqizhi:
#include <bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long int read() { int x = 0; bool f = false; char c = getchar(); while(c < '0' || c > '9') f |= (c == '-'), c = getchar(); while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar(); return f ? -x : x; } const ll mod = 998244353; ll qpow(ll a, ll b, ll mod) { ll ans = 1; while(b) { if(b & 1) ans = ans * a % mod; b >>= 1; a = a * a % mod; } return ans; } ll a0, a1, b, p0, p1, p2; ll dfs(ll x, ll y) { if(x == y) return p0 * p2 % mod; if(x < y) { int k = y / x; if(y % x == 0) --k; return dfs(x, y - k * x) * qpow(p0 * p2 % mod, k, mod) % mod; }else { int k = x / y; if(x % y == 0) --k; return ((dfs(x - k * y, y) - 1 + mod) % mod * qpow(p1 * p2 % mod, k, mod) % mod + 1) % mod; } } void solve() { int x = read(), y = read(); a0 = read(), a1 = read(), b = read(); p0 = a0 * qpow(b, mod - 2, mod) % mod, p1 = a1 * qpow(b, mod - 2, mod) % mod, p2 = (1 - p0 - p1 + mod + mod) % mod; p2 = qpow((1 - p2 + mod) % mod, mod - 2, mod); printf("%lld\n", dfs(x, y)); } int main() { // ios::sync_with_stdio(0); // cin.tie(0), cout.tie(0); int T = read(); while(T--) solve(); return 0; }
E 结论,最短路
dztlb 大神秒了,我看都没看。
看起来就是简单题详见题解吧。
code by dztlb:
#include <bits/stdc++.h> using namespace std; #define int long long const int N=2e5+5; const int M=2e6+5; const int inf=1e18; #define ll long long #define ull unsigned long long int T,n,m,d,k; int id[N][2]; int s[N]; int lim[N][2],tot,head[N*2]; int f[N][2],pre[N][2]; int p[N*4],L,R; struct node{ int to,nxt; }e[M<<1]; void add(int u,int v){ e[++tot].to=v,e[tot].nxt=head[u],head[u]=tot; } void get_new(bool fl,int now,int u,int v){ // if(u==3){ // cout<<"!!\n"; // cout<<fl<<' '<<now<<' '<<u<<' '<<v<<endl; // } if(lim[u][fl]>now&&f[u][fl]==inf){ // cout<<u<<endl; f[u][fl]=now; pre[u][fl]=v; if(fl) p[++R]=u+n; else p[++R]=u; } } int ans[N*4][2],la[2]; signed main(){ ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin>>T; while(T--){ cin>>n>>m>>d; for(int i=1;i<=n;++i) id[i][0]=i,id[i][1]=n+i; for(int i=0;i<=n;++i){ f[i][0]=f[i][1]=lim[i][0]=lim[i][1]=inf; pre[i][0]=0; pre[i][1]=0; } tot=0; for(int i=1;i<=2*n;++i) head[i]=0; for(int i=1,u,v;i<=m;++i){ cin>>u>>v; add(u,v),add(v,u); // add(id[u][0],id[v][1]); // add(id[u][1],id[v][0]); // add(id[v][1],id[u][0]); // add(id[v][0],id[u][1]); } cin>>k; for(int i=1;i<=k;++i){ cin>>s[i]; p[i]=id[s[i]][0]; lim[s[i]][0]=0; } L=1,R=k; while(L<=R){ int u=p[L]; ++L; bool fl=0; if(u>n) fl=1,u-=n; // cout<<u<<' '<<fl<<'\n'; int now=lim[u][fl]; if(now>=d) continue; for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; // cout<<v<<"!!!\n"; // cout<<lim[v][!fl]<<endl; if(lim[v][!fl]==inf){ lim[v][!fl]=now+1; p[++R]=id[v][!fl]; } } } // lim done // for(int i=1;i<=n;++i){ // cout<<i<<' '<<lim[i][0]<<' '<<lim[i][1]<<'\n'; // } L=1,R=0; get_new(0,0,1,0); // cout<<f[1][0]<<' '<<f[1][1]<<"askljdsjlak"<<endl; while(R>=L){ int u=p[L]; ++L; bool fl=0; if(u>n) fl=1,u-=n; // cout<<u<<' '<<fl<<"!!!\n"; for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; // cout<<v<<"???????\n"; int now=f[u][fl]+1; get_new(!fl,now,v,u); } } la[0]=la[1]=inf; for(int ty=0;ty<=1;++ty){ int tmp=ty,now=n; if(f[n][ty]!=inf){ la[ty]=1; while(now){ ans[la[ty]][ty]=now; now=pre[now][tmp]; ++la[ty]; tmp=!tmp; } } } if(la[0]==inf&&la[1]==inf){ cout<<-1<<'\n'; continue; } if(la[0]>la[1]){ cout<<la[1]-2<<'\n'; for(int i=la[1]-1;i>=1;--i){ cout<<ans[i][1]<<' '; } cout<<'\n'; }else{ cout<<la[0]-2<<'\n'; for(int i=la[0]-1;i>=1;--i){ cout<<ans[i][0]<<' '; } cout<<'\n'; } } return 0; } /* 1 7 8 2 1 2 2 3 3 7 2 5 5 6 3 6 1 4 4 5 1 4 */
C 字符串,kmp,结论
场上看到字符串就高超了在这题坐牢 2h 呃呃
以下为官方做法基础上的优化版本。
发现贡献的形式实际上即 Z 函数的定义。枚举每一个后缀 ,求它与整个串的最长公共前缀 ,则其对答案的贡献即:。
考虑每次添加字符 后计算答案的增量。发现添加字符后,对 的影响有如下四种情况:
- 若 ,则 已确定,之后的添加操作均无影响;
- 若 ,且 ,则会令 确定,之后的添加操作均无影响;
- 若 ,且 ,则 ;
- 特判是否有 ,若有则 。
则本次答案的增量,即上述情况 3、4 的 之和乘 。于是考虑维护有贡献的 之和,仅需每次在确定 的同时,减去情况 2 的 的贡献,再加上情况 4 的 的贡献即可。
然而强制在线显然不能直接跑 Z 函数,但是 KMP 支持末尾添加字符,于是考虑如何使用 KMP 实现上述功能。发现若满足 ,则 一定是 的 border,于是仅需考虑枚举 的 border,并考虑下一个字符是否为 即可完成贡献的 之和的修改。
然而并不能直接暴跳 border 太呃呃了,考虑按照 border 的下一个字符的种类路径压缩一下,记 表示前缀 的满足 的第一个 border ,于是每次仅需从 不断跳 和 ,并满足将 的 border 的贡献 减去即可。
可以保证每个 仅会在贡献中被加一次减一次,由字符串的性质可知所有跳 的次数加起来是线性级别的,则暴跳复杂度是正确的。
实现详见代码,总时间复杂度 级别。
#include <bits/stdc++.h> #define LL long long const int kN = 5e5 + 10; int n; int S[kN], A[kN], B[kN]; int fail[kN], fa[kN], vis[kN]; void solve() { LL ans = 0, sumb = 0; fail[1] = 0; for (int i = 1, j = 0; i <= n; ++ i) { std::cin >> S[i] >> A[i] >> B[i]; S[i] = (1ll * ans + S[i]) % n; if (i > 1) { fa[i - 1] = S[fail[i - 1] + 1] == S[i] ? fa[fail[i - 1]] : fail[i - 1]; while (j && S[i] != S[j + 1]) j = fail[j]; if (S[j + 1] == S[i]) ++ j; fail[i] = j; } if (S[i] == S[1]) sumb += B[i]; int p = i - 1; while (p) { int q = fa[p]; if (S[p + 1] != S[i]) { while (p != q) sumb -= B[i - p], p = fail[p]; } p = q; } ans += 1ll * sumb * A[i]; std::cout << ans << "\n"; } } int main() { // freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0); std::cin.tie(0), std::cout.tie(0); std::cin >> n; solve(); return 0; } /* 6 0 1 1 1 2 2 0 3 3 0 4 4 1 5 5 0 6 6 */
写在最后
感觉这场一直在摸鱼呃呃队友太强了都。
学到了什么:
- L:期望的线性性。
- G:辗转相减 辗转相除;
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战