隐藏页面特效

The 2023 ICPC Asia Macau Regional Contest (The 2nd Universal Cup. Stage 15: Macau)

1|0Preface


最幽默的一集

这场开局感觉三个人都有点发昏了,前3h30min就出了两个题,有种要打铁了的感觉

后面想着干脆保个银牌稳扎稳打吧,没想到后面1h连着出了四个题成功冲到银首

最后徐神好像也会G这个博弈了,如果前面不犯病的话感觉还真有机会出7题的说


2|0A. (-1,1)-Sumplete


徐神基本被这题关了一整场,主要刚开始想复杂了一直在想网络流,到后面才发现是个顶针题

不难发现可以把所有的1都转化成1,因此问题变成确定每个格子的值是0/1来满足行列的限制

考虑一行一行地确定行元素,假设当前行需要有x1,那么不妨找出此时列和最大的x列,优先把这些位置置为1即可

直接每次用sort来做的话复杂度是O(n2logn)的,可以用计数排序优化成O(n2),不过没有必要的说

#include <bits/stdc++.h> int n; int m[4000][4000], s[4000][4000], r[4000], c[4000]; void select(int i, int j) { s[i][j] ^= 1; r[i] += m[i][j], c[j] += m[i][j]; m[i][j] = -m[i][j]; } int main() { std::ios::sync_with_stdio(false); std::cin >> n; for(int i = 0; i < n; ++i) { std::string s; std::cin >> s; for(int j = 0; j < n; ++j) m[i][j] = (s[j] == '+' ? 1 : -1); } for(int i = 0; i < n; ++i) std::cin >> r[i], r[i] = -r[i]; for(int i = 0; i < n; ++i) std::cin >> c[i], c[i] = -c[i]; for(int i = 0; i < n; ++i) for(int j = 0; j < n; ++j) if(m[i][j] > 0) select(i, j); for(int i = 0; i < n; ++i) if(c[i] < 0 || r[i] < 0) return std::cout << "No\n", 0; // for(int i = 0; i < n; ++i) { // for(int j = 0; j < n; ++j) std::cout << s[i][j]; // std::cout << "\n"; // } std::vector<int> rr(n), cc(n); for(int i = 0; i < n; ++i) rr[i] = cc[i] = i; std::sort(rr.begin(), rr.end(), [](int x, int y) { return r[x] > r[y]; }); for(int i: rr) { std::sort(cc.begin(), cc.end(), [](int x, int y) { return c[x] > c[y]; }); for(int j: cc) { if(r[i] <= 0) break; if(c[j] > 0) select(i, j); } } // for(int i = 0; i < n; ++i) { // for(int j = 0; j < n; ++j) std::cout << s[i][j]; // std::cout << "\n"; // } // for(int i = 0; i < n; ++i) std::cerr << c[i] << char(i == n - 1 ? 10 : 32); // for(int i = 0; i < n; ++i) std::cerr << r[i] << char(i == n - 1 ? 10 : 32); for(int i = 0; i < n; ++i) if(c[i] || r[i]) return std::cout << "No\n", 0; std::cout << "Yes\n"; for(int i = 0; i < n; ++i) { for(int j = 0; j < n; ++j) std::cout << s[i][j]; std::cout << "\n"; } return 0; }

3|0B. Basic Equation Solving


啥玩意,一眼不可做


4|0C. Bladestorm


唉炉批,唉反向亵渎,唉做不来


5|0D. Graph of Maximum Degree 3


纯诈骗题,主要出题人出了个模998244353直接下了思想钢印让我以为是个Counting Problem,一直到很后面才意识到是个思博题

考虑对于一个m个点的连通块,显然其中至多有3m2条边;如果想要让这个连通块合法,那么其中至少要有2(m1)条边(每种颜色至少是棵树)

因此需要满足一个先决条件:3m22(m1),然后就得到了m4,即符合条件的连通块大小4

那么这题就很trivial了,首先连通块大小为1,2的情况很显然,大小为3时我们需要先找出一条边(u,v)满足这两点之间有两种颜色的边,然后再检验u,v的另外一个邻点是否相同并且边颜色不同即可

连通块大小为4的情况画一下会发现只有一种形态,因此我们可以先搜索出所有连通块,然后对大小为4的暴力检验一下是否合法即可

#include<cstdio> #include<iostream> #include<vector> #include<set> #include<utility> #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=100005; int n,m,x,y,z,ans,vis[N],fa[N]; vector <int> v[N]; set <pi> edges[2]; vector <pi> all[N]; inline int getfa(CI x) { return fa[x]!=x?fa[x]=getfa(fa[x]):x; } inline int check(vector <int> pnt) { RI i,j; for (auto x:pnt) fa[x]=x; for (i=0;i<pnt.size();++i) for (j=1;j<pnt.size();++j) if (edges[0].count(pi(pnt[i],pnt[j]))) fa[getfa(pnt[i])]=getfa(pnt[j]); for (i=1;i<pnt.size();++i) if (getfa(pnt[i])!=getfa(pnt[0])) return 0; for (auto x:pnt) fa[x]=x; for (i=0;i<pnt.size();++i) for (j=1;j<pnt.size();++j) if (edges[1].count(pi(pnt[i],pnt[j]))) fa[getfa(pnt[i])]=getfa(pnt[j]); for (i=1;i<pnt.size();++i) if (getfa(pnt[i])!=getfa(pnt[0])) return 0; return 1; } inline void DFS(CI now,vector <int>& pnt) { vis[now]=1; pnt.push_back(now); for (auto [to,c]:all[now]) if (!vis[to]) DFS(to,pnt); } int main() { RI i; for (scanf("%d%d",&n,&m),i=1;i<=m;++i) { scanf("%d%d%d",&x,&y,&z); all[x].push_back(pi(y,z)); all[y].push_back(pi(x,z)); edges[z].insert(pi(x,y)); edges[z].insert(pi(y,x)); if (z==0) v[x].push_back(y),v[y].push_back(x); } for (i=1;i<=n;++i) for (auto j:v[i]) if (edges[1].count(pi(i,j))) { ++ans; pi it1=pi(-1,-1),it2=pi(-1,-1); for (auto [to,c]:all[i]) if (to!=j) it1=pi(to,c); for (auto [to,c]:all[j]) if (to!=i) it2=pi(to,c); if (it1.fi==-1||it2.fi==-1) continue; if (it1.fi==it2.fi&&it1.se!=it2.se) ++ans; } for (ans/=2,ans+=n,i=1;i<=n;++i) if (!vis[i]) { vector <int> pnt; DFS(i,pnt); if (pnt.size()==4) ans+=check(pnt); } return printf("%d",ans),0; }

6|0E. Inverse Topological Sort


感觉这题很像个CF题啊,经典没有证明全靠猜测

首先不难想到对于{ai}序列,我们找出其所有的逆序对(ai,aj),将所有aiaj都连上边即可满足限制;对于{bi}序列类似

但这样边数会达到O(n2)量级显然就炸了,因此考虑优化,不难发现当我们连xyz后就天然满足了xz的限制,因此不难想到尽量连近的,即:

  • 对于每个aj,找到i<j且下标最大的i使得ai>aj,连边aiaj
  • 对于每个bj,找到i<j且下标最大的i使得bi<bj,连边bibj

不难发现这样可以用最少的连边数量构造出满足两个序列要求的图,然后我们对构造出来的图进行检验

如果图中有环则显然不合法,同时如果在生成的图中再求出其字典序最小/最大的拓扑序和原来不同了,则亦无解

#include<cstdio> #include<iostream> #include<vector> #include<set> #include<queue> #include<algorithm> #include<utility> #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=100005; int n,a[N],b[N],pos[N],deg[N]; vector <int> v[N]; int main() { RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]); for (i=1;i<=n;++i) scanf("%d",&b[i]); set <pi> s; for (i=1;i<=n;++i) pos[a[i]]=i; for (i=n;i>=1;--i) { auto it=s.lower_bound(pi(pos[i],0)); if (it!=s.begin()) v[(--it)->se].push_back(i); s.insert(pi(pos[i],i)); } for (s.clear(),i=1;i<=n;++i) pos[b[i]]=i; for (i=1;i<=n;++i) { auto it=s.lower_bound(pi(pos[i],0)); if (it!=s.begin()) v[(--it)->se].push_back(i); s.insert(pi(pos[i],i)); } for (i=1;i<=n;++i) sort(v[i].begin(),v[i].end()),v[i].erase(unique(v[i].begin(),v[i].end()),v[i].end()); for (i=1;i<=n;++i) for (auto j:v[i]) ++deg[j]; queue <int> q; for (i=1;i<=n;++i) if (!deg[i]) q.push(i); while (!q.empty()) { int now=q.front(); q.pop(); for (auto to:v[now]) if (!--deg[to]) q.push(to); } for (i=1;i<=n;++i) if (deg[i]) return puts("No"),0; for (i=1;i<=n;++i) for (auto j:v[i]) ++deg[j]; priority_queue <int,vector <int>,greater <int>> sml; for (i=1;i<=n;++i) if (!deg[i]) sml.push(i); vector <int> tmp; while (!sml.empty()) { int now=sml.top(); sml.pop(); tmp.push_back(now); for (auto to:v[now]) if (!--deg[to]) sml.push(to); } for (i=1;i<=n;++i) if (a[i]!=tmp[i-1]) return puts("No"),0; for (i=1;i<=n;++i) for (auto j:v[i]) ++deg[j]; priority_queue <int> big; for (i=1;i<=n;++i) if (!deg[i]) big.push(i); tmp.clear(); while (!big.empty()) { int now=big.top(); big.pop(); tmp.push_back(now); for (auto to:v[now]) if (!--deg[to]) big.push(to); } for (i=1;i<=n;++i) if (b[i]!=tmp[i-1]) return puts("No"),0; int cnt=0; for (i=1;i<=n;++i) cnt+=v[i].size(); puts("Yes"); printf("%d\n",cnt); for (i=1;i<=n;++i) for (auto j:v[i]) printf("%d %d\n",i,j); return 0; }

7|0F. Land Trade


做不来,投降捏


8|0G. Parity Game


徐神好像会了但我们前期实在是太唐了没时间写了,坐等徐神赛后补题了


9|0H. Random Tree Parking


本来一点头猪都没有,后面祁神想到一个关键的转化马上就茅塞顿开了

考虑对于某个点i定义vali表示最后选中i点的次数,则最后只要搞个可重全排列就可以快速得到总方案数

考虑我们需要满足哪些性质,对于每个点i,设sumi表示i点子树内val之和,sz表示i点子树大小,depi表示i点深度,则:

  • szisumi,这是显而易见的,不然最后i子树就没法被填满了
  • sumiszidepi,这是由于将子树i填满后,多出的部分会自动向上补,如果溢出的超过了depi那么就会有点飞到外面去了,同样不合法

因此我们得到每个点权值和的范围是[szi,szi+depi],不难发现这个区间的大小只和depi有关

由于题目中保证给出的树纯随机,因此对于所有点的depiO(logn)级别的,那么我们就可以大力背包转移了

总复杂度算不太清楚,要么是两个还是三个logn的来着,总之能跑过去

#include<cstdio> #include<iostream> #include<vector> #define RI register int #define CI const int& using namespace std; const int N=100005,mod=998244353; int n,x,sz[N],dep[N],fact[N],ifac[N]; vector <int> v[N],f[N]; inline int quick_pow(int x,int p=mod-2,int mul=1) { for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul; } inline void init(CI n) { RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod; for (ifac[n]=quick_pow(fact[n]),i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod; } inline void inc(int& x,CI y) { if ((x+=y)>=mod) x-=mod; } inline void DFS(CI now=1) { sz[now]=1; for (auto to:v[now]) dep[to]=dep[now]+1,DFS(to),sz[now]+=sz[to]; RI i,j; int L=0,R=0; static int g[N],tmp[N]; g[0]=1; for (auto to:v[now]) { for (i=L+sz[to];i<=R+sz[to]+dep[to];++i) tmp[i]=0; for (i=L;i<=R;++i) for (j=sz[to];j<=sz[to]+dep[to];++j) inc(tmp[i+j],1LL*g[i]*f[to][j-sz[to]]%mod); for (L+=sz[to],R+=sz[to]+dep[to],i=L;i<=R;++i) g[i]=tmp[i]; } f[now].resize(dep[now]+1); for (i=L;i<=R;++i) for (j=sz[now];j<=sz[now]+dep[now];++j) if (i<=j) inc(f[now][j-sz[now]],1LL*g[i]*ifac[j-i]%mod); } int main() { RI i; for (scanf("%d",&n),i=2;i<=n;++i) scanf("%d",&x),v[x].push_back(i); return init(n),DFS(),printf("%d\n",1LL*fact[n]*f[1][0]%mod),0; }

10|0I. Refresher into Midas


虽然这题定位是签到,但也卡了我们队一会的说

注意到m的范围是有限制的,这就提示我们可能是用O(m)的做法,因此很容易想到按照时间顺序DP

由于我们不可能记下所有时刻两种道具的状态,因此不妨直接拿一个通用的情形来统计,钦定只考虑这种状态

由于开局我们的操作必然是刷钱——刷新球——刷钱,然后两个都进入CD,因此考虑设fi表示到第i秒且两个道具都恰好进入CD时,最多能刷几次钱

转移的话就分两种情况,一种是刷新球CD一好就马上用;另一种是刷新球CD好了先等到最近的一次刷钱CD也转好,然后一起操作

具体情况看代码,最后不要忘记讨论下不用刷新球的情况来更新答案

#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=1e6+5; int t,a,b,m,f[N]; int main() { for (scanf("%d",&t);t;--t) { RI i; for (scanf("%d%d%d",&a,&b,&m),i=0;i<=m;++i) f[i]=0; int ans=0; for (f[0]=2,i=0;i<=m;++i) if (f[i]!=0) { ans=max(ans,f[i]+(m-i)/a); if (i+b<=m) f[i+b]=max(f[i+b],f[i]+(1+b/a)); if (i+(b+a-1)/a*a<=m) f[i+(b+a-1)/a*a]=max(f[i+(b+a-1)/a*a],f[i]+(1+(b+a-1)/a)); } printf("%d\n",ans*160); } return 0; }

11|0J. Teleportation


签到题,但是也腐乳了我们队好久,后面还是徐神想A题的空档给了个思路要再建一排辅助点才通过

考虑将每个点拆成两个i,n+i,钦定每个点i[0,n1]只能用传送操作直接走到点n+(i+ai)modn,花费1的代价

然后不妨在所有的i[n,2n1]这些点之间顺时针连一个环,环上的点边权为1,在这上面走等价于在传送之前拨动指针

最后给每个点n+ii连边权为0的边表示回到真实的点,然后跑一个0/1BFS即可

#include<bits/stdc++.h> using namespace std; #define int long long using pii = pair<int, int>; const int N = 2e5+5; const int INF = (int)1e18+5; int n, x, dis[N]; vector<pii> G[N]; int que[N*2], fr=N, ed=N-1; signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin >> n >> x; for (int i=0; i<n; ++i){ int a; cin >> a; G[i].emplace_back(n+(i+a)%n, 1); G[n+i].emplace_back(n+(i+1)%n, 1); G[n+i].emplace_back(i, 0); } for (int i=0; i<n*2; ++i) dis[i]=INF; dis[0]=0; que[++ed]=0; while (fr<=ed){ int x=que[fr++]; for (auto [v, w] : G[x]){ if (dis[v]>dis[x]+w){ dis[v]=dis[x]+w; if (1==w) que[++ed]=v; else que[--fr]=v; } } } // puts("dis:"); for (int i=0; i<n; ++i) printf("%lld ", dis[i]); puts(""); cout << dis[x] << '\n'; return 0; }

12|0K. Understand


开场就开到的一个题,但好像是防AK题,寄!


13|0Postscript


感觉THU出的题好劲啊,而且好像和我们队都没什么相性打起来巨难受

希望下赛季别遇到THU出题的场QWQ


__EOF__

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