隐藏页面特效

The 1st Universal Cup. Stage 15: Hangzhou

1|0Preface


久违的线下训练,结果因为祁神有急事赶回家了就我和徐神两个人打

开场就感觉有点不对劲怎么没有签到,然后全程对着几个题红温还想了一堆假算法,最后愉悦暴毙

感觉这场题本身质量都挺高的,但最后只写了5个(赛后补了一个),等以后有时间再来补补吧


2|0A. Turn on the Light


徐神开场一个神秘做法直接秒了,我直接呃呃

假设当前答案可能的取值区间为 [l,r],找到其最左和最右的两个四等分点 p,q (p<q),询问 p,q

特判掉问到的点恰好是答案的情况后,如果两次询问后答案不变,则说明区间缩小为 [p+1,q1];否则说明区间缩小为 [l,p1][q+1,r]

实现时可以直接暴力把当前可能的点存下来,总复杂度 O(nlogn),询问次数 2logn40

#include <bits/stdc++.h> int query(int x) { static int pre = 0; std::cout << "? " << x + 1 << std::endl; int res; std::cin >> res; if(res == pre) return std::cout << "! " << x + 1 << std::endl, exit(0), 0; return pre = res; } int main() { std::ios::sync_with_stdio(false); int n; std::cin >> n; int pre = 0; std::vector<int> a(n); for(int i = 0; i < n; ++i) a[i] = i; for(;;) { int l = a.size() / 4, r = a.size() * 3 / 4; int q = (query(a[l]), query(a[r])); std::vector<int> b = std::vector<int> {}; if(q == pre) { for(int i = l + 1; i < r; ++i) b.push_back(a[i]); } else { for(int i = 0; i < l; ++i) b.push_back(a[i]); for(int i = r + 1; i < a.size(); ++i) b.push_back(a[i]); } a = b; // for(int i = 0; i < a.size(); ++i) std::cerr << a[i] << char(i == a.size() - 1 ? 10 : 32); pre = q; } return -1; }

3|0C. Puzzle: Kusabi


显然根据题意一个子树不能匹配得留到之后操作的最多只有一个点,现在就讨论贪心地把子树匹配完即可

当“同”的个数为偶数时,此时所有“同”之间必须按深度两两相配,这时候可以处理“长”或者“短”个数差值 1 的情况

若“短”的个数和“长”的个数相同,那显然就是排序后一一比较;否则若“短”的个数少 1,则贪心地给每个“短”找一个后继匹配是最优的;“短”的个数多 1 亦同理

当“同”的个数为奇数时,则只能上传多出的那个数

实现的时候注意细节,总复杂度 O(nlogn)

#include <bits/stdc++.h> enum { NONE = 0, TONG, CHANG, DUAN, }; void quit() { std::cout << "NO\n"; exit(0); } struct label { int type, depth, id; }; int n; std::vector<std::pair<int, int>> ans; std::vector<std::vector<int>> ch; std::vector<int> nt; label dfs(int cur, int depth = 1) { // std::cerr << "cur = " << cur << char(10); std::vector<label> chang, duan, tong; auto push_back = [&](label l) { switch(l.type) { case CHANG: chang.push_back(l); break; case DUAN: duan.push_back(l); break; case TONG: tong.push_back(l); break; } }; push_back({ nt[cur], depth, cur }); for(auto ch: ch[cur]) push_back(dfs(ch, depth + 1)); // std::cerr << "cc cur = " << cur << char(10); // std::cerr << "tong.size() = " << tong.size() << ", duan.size() == " << duan.size() << ", chang.size() == " << chang.size() << char(10); if(duan.size() > chang.size() + 1 || chang.size() > duan.size() + 1) quit(); if(tong.size() % 2 == 1 && duan.size() != chang.size()) quit(); std::sort( tong.begin(), tong.end(), [](label a, label b) { return a.depth < b.depth; }); std::sort( duan.begin(), duan.end(), [](label a, label b) { return a.depth < b.depth; }); std::sort(chang.begin(),chang.end(), [](label a, label b) { return a.depth < b.depth; }); if(tong.size() % 2 == 0) { // std::cerr << "debug1 cur = " << cur << " \n"; for(int i = 0; i < tong.size(); i += 2) { if(tong[i].depth != tong[i + 1].depth) quit(); ans.emplace_back(tong[i].id, tong[i + 1].id); } if(duan.size() == chang.size()) { for(int i = 0; i < duan.size(); ++i) { if(duan[i].depth >= chang[i].depth) quit(); ans.emplace_back(duan[i].id, chang[i].id); } return { NONE, 0, 0 }; } else if(duan.size() < chang.size()) { label res = {NONE, 0, 0}; for(int i = 0, j = 0; i < duan.size(); ++i) { while(duan[i].depth >= chang[j].depth) { if(res.type == NONE) res = chang[j++]; else quit(); } ans.emplace_back(duan[i].id, chang[j++].id); } if(res.type == NONE) res = chang.back(); return res; } else { label res = {NONE, 0, 0}; for(int i = chang.size() - 1, j = duan.size() - 1; ~i; --i) { while(duan[j].depth >= chang[i].depth) { if(res.type == NONE) res = duan[j--]; else quit(); } ans.emplace_back(duan[j--].id, chang[i].id); } if(res.type == NONE) res = duan.front(); return res; } } else { // std::cerr << "debug2 cur = " << cur << " \n"; for(int i = 0; i < duan.size(); ++i) { if(duan[i].depth >= chang[i].depth) quit(); ans.emplace_back(duan[i].id, chang[i].id); } label res = { NONE, 0, 0 }; for(int i = 0; i < tong.size(); ) { if(i == tong.size() - 1 || tong[i].depth != tong[i + 1].depth) { if(res.type != NONE) quit(); res = tong[i++]; } else { ans.emplace_back(tong[i].id, tong[i + 1].id); i += 2; } } return res; } } int main() { std::ios::sync_with_stdio(false); std::cin >> n; ch.resize(n + 1), nt.resize(n + 1); for(int i = 2, _, p; i <= n; ++i) { std::string s; std::cin >> _ >> p >> s; if(s == "Tong") nt[i] = TONG; else if(s == "Chang") nt[i] = CHANG; else if(s == "Duan") nt[i] = DUAN; else nt[i] = NONE; ch[p].push_back(i); } if(dfs(1).type != NONE) quit(); std::cout << "YES\n"; for(auto [a, b]: ans) std::cout << a << ' ' << b << char(10); return 0; }

4|0D. Master of Both III


刚开始的思路是把状态抽象成一个点,然后跑最短路到全为 0 的状态,但复杂度 O(3n×n) 显然爆炸了

后面徐神发现可以从全为 0 的状态出发,然后做一个高维后缀 min 就可以优化成 O(2n×n)

因为高维后缀 min 不需要容斥,因此代码十分好写

#include <bits/stdc++.h> using llsi = long long signed int; int n; inline llsi shift(llsi s, int y) { s |= s << y | s >> (n - y); s &= (1 << n) - 1; return s; } llsi a[22]; llsi dp[1 << 22]; inline void chkmn(llsi &a, const llsi &b) { if(b < a) a = b; } int main () { std::ios::sync_with_stdio(false); std::cin >> n; for(int i = 0; i < n; ++i) std::cin >> a[i]; memset(dp, 0x3f, sizeof(dp)); dp[1] = 0; for(int i = 1; i < (1 << n); ++i) { llsi sta = i; for(int j = 1; j < n; ++j) { chkmn(dp[shift(sta, n - j)], dp[sta] + a[j]); } // std::cout << "dp[" << i << "] = " << dp[i] << char(10); } for(int i = (1 << n) - 1; ~i; --i) { llsi sta = i; for(int j = 0; j < n; ++j) if(!(sta >> j & 1)) { chkmn(dp[sta], dp[sta | 1 << j]); } } for(int i = 1; i < (1 << n); ++i) { // std::cout << "dp[" << i << "] = " << dp[i] << char(10); } llsi ans = 0; constexpr llsi mod = 998244353; for(int i = 1; i < (1 << n); ++i) ans = (ans + dp[i] * i) % mod; std::cout << ans << char(10); return 0; }

5|0E. Puzzle: Tapa


很有意思的思维题,首先不难发现最外围一圈要特别处理,要选的空白格就只能在两个相邻的 clue 中间

而中间的情况就比较特殊,虽然允许一个空白格处理对角线相邻的四个 clue,但不难发现这种情况一定可以拆成在两对相邻的 clue 之间放上空白格

因此这题就很简单了,把所有可以匹配的 clue 之间连边,根据网格图的性质这一定是个二分图,看看是否有完美匹配即可

用匈牙利算法实现,复杂度 O((nm)3),实际上因为边数很少所以远远跑不满

#include<cstdio> #include<iostream> #include<vector> #include<array> #include<algorithm> #define RI register int #define CI const int& using namespace std; const int N=105,dx[4]={0,2,0,-2},dy[4]={2,0,-2,0}; int n,m,vis[N*N],match[N*N],x[N*N],y[N*N]; char s[N][N]; vector <int> A,B; vector <array <int,3>> v[N*N]; inline int ID(CI x,CI y) { return (x-1)*(2*m-1)+y; } inline void addedge(int a,int b,int c,int d,CI e,CI f) { if (!(s[a][b]=='2'||s[a][b]=='4'||s[a][b]=='7')) return; if (!(s[c][d]=='2'||s[c][d]=='4'||s[c][d]=='7')) return; if (((a+b)/2)%2) swap(a,c),swap(b,d); v[ID(a,b)].push_back({ID(c,d),e,f}); } inline bool find(CI now,CI idx) { for (auto [to,ex,ey]:v[now]) if (vis[to]!=idx) { vis[to]=idx; if (!match[to]||find(match[to],idx)) return match[to]=now,x[to]=ex,y[to]=ey,1; } return 0; } int main() { RI i,j,k; scanf("%d%d",&n,&m); for (i=1;i<=2*n-1;++i) scanf("%s",s[i]+1); for (i=1;i<=2*n-1;i+=2) for (j=1;j<=2*m-1;j+=2) { if (!(s[i][j]=='2'||s[i][j]=='4'||s[i][j]=='7')) continue; if (((i+j)/2)%2) B.push_back(ID(i,j)); else A.push_back(ID(i,j)); } for (i=1;i+2<=2*n-1;i+=2) addedge(i,1,i+2,1,i+1,1),addedge(i,2*m-1,i+2,2*m-1,i+1,2*m-1); for (j=1;j+2<=2*m-1;j+=2) addedge(1,j,1,j+2,1,j+1),addedge(2*n-1,j,2*n-1,j+2,2*n-1,j+1); for (i=3;i<=2*n-3;i+=2) for (j=3;j<=2*m-3;j+=2) { if (!(s[i][j]=='2'||s[i][j]=='4'||s[i][j]=='7')) continue; for (k=0;k<4;++k) { int ii=i+dx[k],jj=j+dy[k]; if (ii<3||ii>2*n-3||jj<3||jj>2*m-3) continue; if (!(s[ii][jj]=='2'||s[ii][jj]=='4'||s[ii][jj]=='7')) continue; addedge(i,j,ii,jj,(i+ii)/2,(j+jj)/2); } } //sort(A.begin(),A.end()); A.erase(unique(A.begin(),A.end()),A.end()); //sort(B.begin(),B.end()); B.erase(unique(B.begin(),B.end()),B.end()); if (A.size()!=B.size()) return puts("NO"),0; int ret=0,cnt=0; for (auto v:A) ret+=find(v,++cnt); if (ret!=A.size()) return puts("NO"),0; for (auto v:B) s[x[v]][y[v]]='#'; for (puts("YES"),i=1;i<=2*n-1;++i,putchar('\n')) for (j=1;j<=2*m-1;++j) { char ch=s[i][j]; if (ch=='.') ch='#'; else if (ch=='#') ch='.'; putchar(ch); } return 0; }

6|0F. Classic: Classical Problem


比赛一上来想了个 bitset 的神秘做法,然后写完交了一发才发现复杂度假了

后面徐神跟我说了一个fix的思路(后面发现沿着这个思路就能过了),但被我扔了跑去想原根的做法了,结果后面又出了个假算法死的透透的

赛后看官方 Tutorial 发现正解是原根转化+二分答案+FFT优化循环卷积的 O(plog2p) 的构式玩意,难写地一批

后面看其他人过的代码发现了一种很好写的根号分治做法,看了下感觉就是赛时思路的优化

考虑若 p=n,此时所有数构成完全剩余系,则答案显然为 p,否则当 n 变小是就会出现某些数不出现

根据不出现的数个数 pn 进行根号分治:

  • pnp,则枚举每个 c,再暴力枚举所有没出现的数并更新答案;
  • pn>p,则枚举每个 c,并枚举 mex 的值并暴力向后找到第一个不出现的位置,因为此时空洞数量 >p,因此平均往后移动的次数是 O(p) 级别的;

综上,总复杂度 O(pp),还非常好写

#include<cstdio> #include<iostream> #include<vector> #include<cmath> #define RI register int #define CI const int& using namespace std; const int N=200005; int t,n,p,x,a[N],ans[N]; inline int quick_pow(int x,int mod,int p,int mul=1) { for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul; } int main() { //freopen("F.in","r",stdin); for (scanf("%d",&t);t;--t) { RI i,c; for (scanf("%d%d",&n,&p),i=0;i<p;++i) a[i]=0; for (i=1;i<=n;++i) scanf("%d",&x),a[x]=1; if (!a[0]) { puts("1 1\n0"); continue; } else ans[0]=1; if (p-n<=(int)sqrt(p)) { vector <int> vec; for (i=0;i<p;++i) if (!a[i]) vec.push_back(i); for (c=1;c<p;++c) { int mex=p; for (auto x:vec) mex=min(mex,(int)(1LL*c*x%p)); ans[c]=mex; } } else { for (c=1;c<p;++c) { int mex=1,inv=quick_pow(c,p,p-2); while (a[1LL*mex*inv%p]) ++mex; ans[c]=mex; } } int mx=0; vector <int> num; for (i=0;i<p;++i) mx=max(mx,ans[i]); for (i=0;i<p;++i) if (ans[i]==mx) num.push_back(i); printf("%d %d\n",num.size(),mx); for (auto x:num) printf("%d ",x); putchar('\n'); } return 0; }

7|0H. Classic: N Real DNA Pots


开场看了一眼就会了,感觉还是很自然的

考虑二分答案 s,检验的话我们先讲所有点按照横坐标排序

考虑对于 i<j,合法的条件为 yjyixjxis,即 yjs×xjyis×xi

因此按照 yis×xi 的值做一个最长上升子序列即可,总复杂度 O(nlognlog1ϵ)

#include<cstdio> #include<iostream> #include<algorithm> #include<utility> #define RI register int #define CI const int& #define fi first #define se second using namespace std; typedef long double LDB; typedef pair <int,int> pi; const int N=100005; const LDB EPS=1e-9; int n,m,k,a[N]; pi p[N]; LDB val[N],rst[N]; class Tree_Array { private: int bit[N]; public: #define lowbit(x) (x&-x) inline void init(void) { for (RI i=0;i<=m;++i) bit[i]=0; } inline int get(RI x,int ret=0) { for (;x;x-=lowbit(x)) ret=max(ret,bit[x]); return ret; } inline void add(RI x,CI y) { for (;x<=m;x+=lowbit(x)) bit[x]=max(bit[x],y); } #undef lowbit }BIT; inline int check(const LDB& slp) { RI i; for (i=1;i<=n;++i) val[i]=rst[i]=1.0L*p[i].se-slp*p[i].fi; sort(rst+1,rst+n+1); m=unique(rst+1,rst+n+1)-rst-1; int ret=0; for (BIT.init(),i=1;i<=n;++i) { int tmp=lower_bound(rst+1,rst+m+1,val[i])-rst; int cur=BIT.get(tmp)+1; ret=max(ret,cur); BIT.add(tmp,cur); } return ret; } int main() { RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d%d",&p[i].fi,&p[i].se); sort(p+1,p+n+1); LDB l=-1e9,r=1e9,mid; while (r-l>EPS) { mid=(l+r)/2.0; if (check(mid)>=k) l=mid; else r=mid; } return printf("%.8Lf",l),0; }

8|0Postscript


感觉可做题还有B和I都没写,等到时候有时间再补吧


__EOF__

本文作者hl666
本文链接https://www.cnblogs.com/cjjsb/p/18294946.html
关于博主:复活的ACM新生,目前爱好仅剩Gal/HBR/雀魂/单机/OSU
版权声明:转载请注明出处
声援博主:欢迎加QQ:2649020702来DD我
posted @   空気力学の詩  阅读(232)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2023-07-10 UESTC 2023 Summer Training #01 Div.2
点击右上角即可分享
微信分享提示