隐藏页面特效

The 1st Universal Cup. Stage 19: America

1|0Preface


中秋加训,发现 Ucup 里好多比赛要么之前做过,要么就是太毒瘤(有场比赛直接把模 998244353 写在题目名里了),因此就直接跳到这场北美区决赛了

这场前期因为卡题意以及卡签到打的还是挺难受的,不过好在恶心归恶心题目还是都能出,最后也是堪堪写了 9 题

由于这场没找到题解(其实找到了,但好像要FQ)剩下的题就先坑着了


2|0A. Allergen Testing


刚开始想的是如果只有一天就是经典的二进制分组模型,天数较多的话就考虑平均分一下,交上去发现 WA 飞了

后面徐神出手发现其实就是把原来的二进制编码改成 d+1 进制编码,因此答案就是 logd+1n

for _ in range(int(input())): n, k = map(int, input().split()) ans = 0 k += 1 i = 1 while i < n: ans += 1 i *= k print(ans)

3|0B. A Tree and Two Edges


大力分讨题

考虑先求出一个生成树,并将多出的两条边记为 (a,b),(c,d)

不难发现答案的上界就是 4,要单纯的人为区分 1,2,3,4 的 Case 很复杂,但我们可以枚举所有可能的路径并检验其是否合法

uabcdv 的路径来举例,其实只要看 ua,bc,dv 这三部分路径上的点是否有重复

检验的话也很简单,用树剖把路径拆成若干个区间,判断这些区间是否有交即可,直接排序后判断即可

#include<cstdio> #include<iostream> #include<vector> #include<algorithm> #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=50005; int n,q,x,y,fa[N],a,b,c,d; vector <int> v[N]; inline int getfa(CI x) { return fa[x]!=x?fa[x]=getfa(fa[x]):x; } namespace Tree_Division { int dep[N],dfn[N],sz[N],top[N],anc[N],son[N],idx; inline void DFS1(CI now=1,CI fa=0) { sz[now]=1; dep[now]=dep[fa]+1; anc[now]=fa; for (auto to:v[now]) if (to!=fa) { DFS1(to,now); sz[now]+=sz[to]; if (sz[to]>sz[son[now]]) son[now]=to; } } inline void DFS2(CI now=1,CI topf=1) { dfn[now]=++idx; top[now]=topf; if (son[now]) DFS2(son[now],topf); for (auto to:v[now]) if (to!=anc[now]&&to!=son[now]) DFS2(to,to); } inline void modify(int x,int y,vector <pi>& Itv) { while (top[x]!=top[y]) { if (dep[top[x]]<dep[top[y]]) swap(x,y); Itv.push_back({dfn[top[x]],dfn[x]}); x=anc[top[x]]; } if (dep[x]<dep[y]) swap(x,y); Itv.push_back({dfn[y],dfn[x]}); } }; using namespace Tree_Division; inline bool check(vector <int> vec) { vector <pi> Itv; for (RI i=0;i+1<vec.size();i+=2) modify(vec[i],vec[i+1],Itv); sort(Itv.begin(),Itv.end()); for (RI i=0;i+1<Itv.size();++i) if (Itv[i].se>=Itv[i+1].fi) return 0; return 1; } int main() { scanf("%d%d",&n,&q); for (RI i=1;i<=n;++i) fa[i]=i; for (RI i=1;i<=n+1;++i) { scanf("%d%d",&x,&y); if (getfa(x)==getfa(y)) { if (a==0) a=x,b=y; else c=x,d=y; continue; } v[x].push_back(y); v[y].push_back(x); fa[getfa(x)]=getfa(y); } DFS1(); DFS2(); for (RI i=1;i<=q;++i) { scanf("%d%d",&x,&y); int ans=1; ans+=check({x,a,b,y}); ans+=check({x,b,a,y}); ans+=check({x,c,d,y}); ans+=check({x,d,c,y}); ans+=check({x,a,b,c,d,y}); ans+=check({x,a,b,d,c,y}); ans+=check({x,b,a,c,d,y}); ans+=check({x,b,a,d,c,y}); ans+=check({x,c,d,a,b,y}); ans+=check({x,c,d,b,a,y}); ans+=check({x,d,c,a,b,y}); ans+=check({x,d,c,b,a,y}); printf("%d\n",ans); } return 0; }

4|0C. Broken Minimum Spanning Tree


考虑按照边权从小到大枚举所有的非树边 (u,v,w),在树上遍历一遍找出 uv 的路径上最长的边 w

w<w 则一定要发生替换,且由于我们枚举的顺序,当前替换了一定最优,所以直接模拟这个过程即可

#include<cstdio> #include<iostream> #include<set> #include<vector> #include<array> #include<algorithm> #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=3005; int n,m,x[N],y[N],z[N],in[N],pre[N]; pi lst[N]; multiset <array <int,3>> v[N]; inline void addedge(CI id) { v[x[id]].insert({y[id],z[id],id}); v[y[id]].insert({x[id],z[id],id}); } inline void deledge(CI id) { v[x[id]].erase(v[x[id]].find({y[id],z[id],id})); v[y[id]].erase(v[y[id]].find({x[id],z[id],id})); } inline void find_path(CI now) { for (auto [to,w,id]:v[now]) if (pre[to]==0) pre[to]=now,lst[to]={w,id},find_path(to); } int main() { scanf("%d%d",&n,&m); vector <pi> E,ans; for (RI i=1;i<=m;++i) { scanf("%d%d%d",&x[i],&y[i],&z[i]); if (i<n) in[i]=1,addedge(i); else E.push_back({z[i],i}); } sort(E.begin(),E.end()); for (auto [_,id]:E) { if (in[id]) continue; for (RI i=1;i<=n;++i) pre[i]=0; pre[x[id]]=-1; find_path(x[id]); pi mx={0,0}; for (int now=y[id];now!=x[id];now=pre[now]) mx=max(mx,lst[now]); if (mx.fi>z[id]) { ans.push_back({id,mx.se}); deledge(mx.se); in[mx.se]=0; addedge(id); in[id]=1; } } printf("%d\n",ans.size()); for (auto [x,y]:ans) printf("%d %d\n",x,y); return 0; }

5|0F. Four Square


沟槽大力分讨题,大致思路就是先算出矩形的边长,然后考虑一定存在一个矩形 cover 了这个边长,递归处理即可

#include <bits/stdc++.h> struct R { int h, w; }; using vii = std::vector<R>; #define run(i) ({ std::cout << i << "\n"; return 0; }) bool solve(int h, int w, vii v) { // std::cerr << "solve(" << h << ", " << w << ")\n"; if(h > w) std::swap(h, w); if(h < 0) return false; if(h == 0) return true; auto fres = [&v] (int i, int j) { vii res; for(int k = 0; k < v.size(); ++k) if(k != i && k != j) res.emplace_back(v[k]); return res; }; for(int i = 0; i < v.size(); ++i) { if(v[i].w > w) return false; } for(int i = 0; i < v.size(); ++i) { if(v[i].w == w && solve(h - v[i].h, w, fres(i, -1))) return true; if(v[i].w == h && solve(w - v[i].h, h, fres(i, -1))) return true; if(v[i].h == w && solve(h - v[i].w, w, fres(i, -1))) return true; if(v[i].h == h && solve(w - v[i].w, h, fres(i, -1))) return true; } for(int i = 0; i < v.size(); ++i) for(int j = i + 1; j < v.size(); ++j) { if(v[i].w == v[j].w && v[i].h + v[j].h == w && solve(h - v[i].w, w, fres(i, j))) return true; if(v[i].h == v[j].h && v[i].w + v[j].w == w && solve(h - v[i].h, w, fres(i, j))) return true; if(v[i].w == v[j].h && v[i].h + v[j].w == w && solve(h - v[i].w, w, fres(i, j))) return true; if(v[i].h == v[j].w && v[i].w + v[j].h == w && solve(h - v[i].h, w, fres(i, j))) return true; if(v[i].w == v[j].w && v[i].h + v[j].h == h && solve(w - v[i].w, h, fres(i, j))) return true; if(v[i].h == v[j].h && v[i].w + v[j].w == h && solve(w - v[i].h, h, fres(i, j))) return true; if(v[i].w == v[j].h && v[i].h + v[j].w == h && solve(w - v[i].w, h, fres(i, j))) return true; if(v[i].h == v[j].w && v[i].w + v[j].h == h && solve(w - v[i].h, h, fres(i, j))) return true; } return false; } int main() { std::ios::sync_with_stdio(false); int ss = 0, t; vii rect(4, {0, 0}); for(auto &[h, w]: rect) { std::cin >> h >> w; if(h > w) std::swap(h, w); ss += h * w; } t = std::sqrt(ss); if(t * t != ss) run(0); std::cout << solve(t, t, rect) << char(10); return 0; }

6|0G. Frequent Flier


小清新贪心题

不难发现我们可以用一个类似滑动窗口的东西维护当前区间内选了多少数,若这个值大于等于 k 则可以先贪心地不操作

否则考虑在当前区间内的某些位置选数,显然这个位置是越靠后越好;但因为某个位置可能填到了上界,因此不得不转而选择前面的位置

用经典 trick,拿一个并查集来维护从每个位置往前第一个还可以填数的位置,势能分析一下会发现复杂度很对

#include<cstdio> #include<iostream> #define int long long #define RI register int #define CI const int& using namespace std; const int N=400005; int n,m,k,f[N],g[N],fa[N]; inline int getfa(CI x) { return fa[x]!=x?fa[x]=getfa(fa[x]):x; } signed main() { scanf("%lld%lld%lld",&n,&m,&k); for (RI i=1;i<=n;++i) scanf("%lld",&f[i]); for (RI i=1;i<=n+m;++i) fa[i]=i; int sum=0,ans=0; for (RI i=1;i<=n+m-1;++i) { if (i-m>=1) sum-=g[i-m]; if (sum>=k) continue; int x=getfa(i); while (x>0&&x>i-m&&sum<k) { int tmp=min(f[x]-g[x],k-sum); g[x]+=tmp; ans+=tmp; sum+=tmp; if (f[x]==g[x]) fa[x]=getfa(x-1),x=getfa(x); } } return printf("%lld",ans),0; }

7|0I. Power of Divisors


徐神开场写的签,我题意都不知道

from math import (pow, floor) x = int(input()) if(x == 1): print(x) exit(0) def f(n: int): count = 0 i = 1 while i * i < n: if n % i == 0: count += 2 i += 1 if i * i == n: count += 1 return count ans = 1_0000_0000_0000 for i in range(2, 64): dx = floor(pow(x, 1.0 / i)) while dx ** i > x: dx -= 1 while (dx + 1) ** i <= x: dx += 1 if dx ** i != x: continue if f(dx) == i: ans = min(ans, dx) print(-1 if ans == 1_0000_0000_0000 else ans)

8|0J. Repetitive String Invention


很纯的 string 题,那当然是得让徐神 solo 了

#include <bits/stdc++.h> constexpr int $n = 1605; int go[$n][26], fail[$n], len[$n], las = 1, O = 1; std::vector<int> occ[$n], ch[$n]; void insert(char c) { c -= 'a'; int p = las, np = las = ++O; len[np] = len[p] + 1; memset(go[np], 0, sizeof(go[np])); for(; p && !go[p][c]; p = fail[p]) go[p][c] = np; if(!p) { fail[np] = 1; return ; } int q = go[p][c]; if(len[q] == len[p] + 1) { fail[np] = q; return ; } int nq = ++O; memcpy(go[nq], go[q], sizeof(go[nq])); len[nq] = len[p] + 1; fail[nq] = fail[q]; fail[np] = fail[q] = nq; for(; p && go[p][c] == q; p = fail[p]) go[p][c] = nq; return ; } bool vis[$n][$n]; int hkr[$n][$n]; int n; void dfs(int cur) { for(auto ch: ch[cur]) { dfs(ch); for(int i = 1; i <= n; ++i) occ[cur][i] += occ[ch][i]; } } int main() { std::ios::sync_with_stdio(false); std::string s; std::cin >> s; n = s.size(); for(int i = 0; i < n; ++i) insert(s[i]); for(int i = 1; i <= O; ++i) ch[fail[i]].emplace_back(i), occ[i].assign(n + 1, 0); for(int i = 1, cur = 1; i <= n; ++i) { cur = go[cur][s[i - 1] - 'a']; occ[cur][i] = 1; } dfs(1); for(int i = 1; i <= O; ++i) for(int j = 1; j <= n; ++j) occ[i][j] += occ[i][j - 1]; for(int l = 1; l <= n; ++l) for(int r = l, cur = 1; r <= n; ++r) hkr[l][r] = cur = go[cur][s[r - 1] - 'a']; int64_t ans = 0; for(int l = 1; l <= n; ++l) { for(int r = l, cur = 1; r <= n; ++r) { cur = go[cur][s[r - 1] - 'a']; assert(cur == hkr[l][r]); // if(vis[cur][r - l + 1]) continue; // vis[cur][r - l + 1] = 1; // std::cerr << "l, r = " << l << ", " << r << char(10); for(int k = r + (r - l + 1); k <= n; ++k) if(occ[cur][k] - occ[cur][k - 1] == 1) { // std::cerr << " k = " << k << char(10); ans += 1; if(k == r + (r - l + 1)) { continue; } int cl = r + 1, cr = k - (r - l + 1), cc = hkr[cl][cr]; // std::cerr << " cl, cr = " << cl << ", " << cr << char(10); int prev = ans; ans += occ[cc][l - 1]; if(k + (cr - cl) < n) ans += occ[cc][n] - occ[cc][k + (cr - cl + 1) - 1]; // std::cerr << " update = " << ans - prev << char(10); } } } std::cout << ans << char(10); return 0; }

9|0K. Space Alignment


读懂题目就很简单的一个题

首先很容易找出每一行是括号嵌套的第几层,并且可以求出每一行空格和 Tab 的数量,记作一个二元组

那么对于同层的所有二元组,若它们全部相同,则无法推出有用信息;否则其实以及可以唯一确定答案的值了,接下来要做的就是带回去检验一下

如果单独靠每层的信息无法推出答案,则考虑将相邻层的二元组作差,然后进行和上面类似的操作即可

#include<cstdio> #include<iostream> #include<vector> #include<utility> #include<cstring> #include<algorithm> #define RI register int #define CI const int& #define fi first #define se second using namespace std; const int N=105; typedef pair <int,int> pi; int n,a[N],dep[N],stk[N],top,mxdep; pi st[N]; vector <pi> bkt[N]; char s[1005]; inline bool check(CI k) { if (k<=0) return 0; static int res[N]; for (RI i=0;i<=mxdep;++i) { vector <int> tmp; for (auto [x,y]:bkt[i]) tmp.push_back(x+y*k); sort(tmp.begin(),tmp.end()); tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end()); if (tmp.size()!=1) return 0; res[i]=tmp.back(); } if (mxdep==0) return 1; vector <int> tmp; for (RI i=1;i<=mxdep;++i) tmp.push_back(res[i]-res[i-1]); sort(tmp.begin(),tmp.end()); tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end()); if (tmp.size()!=1) return 0; return tmp.back()>0; } int main() { scanf("%d",&n); for (RI i=1;i<=n;++i) { scanf("%s",s+1); int len=strlen(s+1); pi cur={0,0}; for (RI j=1;j<len;++j) if (s[j]=='s') ++cur.fi; else ++cur.se; st[i]=cur; a[i]=(s[len]=='{'?1:-1); } for (RI i=1;i<=n;++i) { if (a[i]==1) stk[++top]=i; else { int pre=stk[top--]; dep[i]=dep[pre]=top; bkt[top].push_back(st[i]); bkt[top].push_back(st[pre]); mxdep=max(mxdep,top); } } for (RI i=0;i<=mxdep;++i) { sort(bkt[i].begin(),bkt[i].end()); bkt[i].erase(unique(bkt[i].begin(),bkt[i].end()),bkt[i].end()); if (bkt[i].size()==1) continue; auto [a,b]=bkt[i][0]; auto [c,d]=bkt[i][1]; if ((a==c&&b!=d)||(a!=c&&b==d)) return puts("-1"),0; if (abs(c-a)%abs(b-d)!=0) return puts("-1"),0; int k=(c-a)/(b-d); if (check(k)) return printf("%d",k),0; else return puts("-1"),0; } if (mxdep==0) return puts("1"),0; vector <pi> tmp; for (RI i=1;i<=mxdep;++i) { auto [a,b]=bkt[i-1].back(); auto [c,d]=bkt[i].back(); tmp.push_back({c-a,d-b}); } sort(tmp.begin(),tmp.end()); tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end()); if (tmp.size()==1) { if (check(1)) return puts("1"),0; else return puts("-1"),0; } auto [a,b]=tmp[0]; auto [c,d]=tmp[1]; if ((a==c&&b!=d)||(a!=c&&b==d)) return puts("-1"),0; if (abs(c-a)%abs(b-d)!=0) return puts("-1"),0; int k=(c-a)/(b-d); if (check(k)) return printf("%d",k),0; else return puts("-1"),0; return 0; }

10|0L. Splitting Pairs


遇到博弈直接打表启动,打了表后也是不难找到规律:

  • n 为偶数时,当且仅当所有 ai 均为奇数时,先手必败;
  • n 为奇数时,当且仅当所有 lowbit(ai) 全相同时,先手必败;

具体证明可以用经典的镜像法则推导

#include<bits/stdc++.h> using namespace std; #define int long long int lb(int x) {return x&(-x);} int solve() { int n; cin >> n; vector<int> vec(n); for (int i=0; i<n; ++i) cin >> vec[i]; if (n%2==0) { for (int i=0; i<n; ++i) if (vec[i]%2==0) return 1; return 0; } for (int i=1; i<n; ++i) { if (lb(vec[0]) != lb(vec[i])) return 1; } return 0; } signed main() { ios::sync_with_stdio(0); cin.tie(0); int t; cin >> t; while (t--) cout << solve() << '\n'; return 0; }

11|0Postscript


美好的中秋假期就在一天网络赛,两天训练中结束了,真是团团又圆圆啊


__EOF__

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