隐藏页面特效

The 2018 ICPC Asia Qingdao Regional Programming Contest (The 1st Universal Cup, Stage 9: Qingdao)

1|0Preface


久违地VP一场,虽然打的挺唐但勉强写出了8题

前中期EFB三开三卡属实有点难受,而且B题这个套路大合集我和徐神两个人写了快200行也占用了一定机时

但好在后面把卡住的题慢慢都写出来了,然后最后40min冲刺L题成功

比较可惜的是I这个开场看的题没有再细想一步,感觉想到在线段树上DP就很容易做了


2|0A. Sequence and Sequence


防AK题,开场看了眼题面一眼不可做直接扔了


3|0B. Kawa Exam


超级经典套路题,徐神1h就开了这道题然后开始写,后面我接力上去写了个DSU on Tree加线段树,这就是我们热血沸腾的组合技啊

首先原问题等价于,每个点有一个颜色,每个连通块的权值定义为其中出现次数最多的颜色的值

图的权值定义为所有连通块的权值和,现在要对于每一条边求出,断掉这条边后图的权值是多少

看到断边和连通性,很容易想到求出桥,显然不是桥的那些边删不删对答案没有影响,可以预先计算出

然后根据经典结论,将一个无向图边双缩点后,得到的一定是个森林,因此只要考虑在树上断一条边求解原问题

树上的断边又等价于选出一个子树,求出它的贡献以及删去整个子树后剩下部分的贡献

直接DSU on Tree扫描子树,同时线段树分别维护子树内和子树外每种颜色出现的次数即可,总复杂度O(nlog2n)

#include <bits/stdc++.h> constexpr int $n = 200005; std::vector<std::pair<int, int>> out[$n]; int dfn[$n], low[$n], tarjan_O; std::vector<std::pair<int, int>> edge; std::vector<bool> is_bridge; int fa[$n]; bool vis[$n]; inline int father(int i) { if(fa[i] == i) return i; return fa[i] = father(fa[i]); } void tarjan(int u, int pre) { low[u] = dfn[u] = ++tarjan_O; vis[u] = true; int son = 0; int pre_cnt = 0; for(auto [v, id]: out[u]) { if(v == pre && pre_cnt == 0) { pre_cnt += 1; continue; } if(!dfn[v]) { son += 1; tarjan(v, u); if(low[u] > low[v]) low[u] = low[v]; if(low[v] > dfn[u]) is_bridge[id] = true; } else if(low[u] > dfn[v]) { low[u] = dfn[v]; } } } int n, m,C; int a[$n], ans[$n], baseline; std::vector<int> hkr[$n]; int ans_partial[$n]; std::map<int, int> rst[$n]; std::map<int, int> dfs1(int cur, int pre) { std::map<int, int> res {}; vis[cur] = true; for(auto hkr: hkr[cur]) res[hkr] += 1; for(auto [ch, id]: out[cur]) if(ch != pre) { auto s = dfs1(ch, cur); if(s.size() > res.size()) std::swap(s, res); for(auto [k, v]: s) res[k] += v; } return res; } #define CI const int& class Segment_Tree { private: int mx[$n<<2]; public: #define TN CI now=1,CI l=1,CI r=C #define LS now<<1,l,mid #define RS now<<1|1,mid+1,r inline void updata(CI pos,CI mv,TN) { if (l==r) return (void)(mx[now]+=mv); int mid=l+r>>1; if (pos<=mid) updata(pos,mv,LS); else updata(pos,mv,RS); mx[now]=std::max(mx[now<<1],mx[now<<1|1]); } inline int query(void) { return mx[1]; } #undef TN #undef LS #undef RS }IN,OUT; int son[$n],sz[$n],bid[$n]; inline void getson(CI now,CI fa=0) { sz[now]=1; son[now]=0; vis[now]=true; for (auto [to,_]:out[now]) if (to!=fa) { getson(to,now); sz[now]+=sz[to]; if (sz[to]>sz[son[now]]) son[now]=to,bid[now]=_; } } inline void travel(CI now,CI fa,CI tp) { for (auto x:hkr[now]) IN.updata(x,tp),OUT.updata(x,-tp); for (auto [to,_]:out[now]) if (to!=fa) travel(to,now,tp); } inline void DSU(CI outer,CI now,CI fa=0,CI id=-1,CI flag=0) { for (auto [to,_]:out[now]) if (to!=fa&&to!=son[now]) DSU(outer,to,now,_,0); if (son[now]) DSU(outer,son[now],now,bid[now],1); for (auto [to,_]:out[now]) if (to!=fa&&to!=son[now]) travel(to,now,1); for (auto x:hkr[now]) IN.updata(x,1),OUT.updata(x,-1); if (~id) ans[id]=outer+IN.query()+OUT.query(); if (!flag) travel(now,fa,-1); } void solve(CI outer,CI root) { for (auto [x,y]:rst[root]) OUT.updata(x,y); getson(root); DSU(outer,root); for (auto [x,y]:rst[root]) OUT.updata(x,-y); } #undef CI void work() { std::cin >> n >> m; C=0; tarjan_O = 0; for(int i = 1; i <= n; ++i) low[i] = dfn[i] = 0; for(int i = 1; i <= n; ++i) std::cin >> a[i],C=std::max(C,a[i]); for(int i = 1; i <= n; ++i) out[i].clear(); edge.resize(m), is_bridge.assign(m, false); for(int i = 0; i < m; ++i) { auto &[f, t] = edge[i]; std::cin >> f >> t; out[f].emplace_back(t, i), out[t].emplace_back(f, i); } for(int i = 1; i <= n; ++i) vis[i] = false; for(int i = 1; i <= n; ++i) if(!vis[i]) tarjan(i, i); // for(int i = 0; i < m; ++i) if(is_bridge[i]) std::cerr << edge[i].first << " <-> " << edge[i].second << char(10); // std::cerr << "-------\n"; // return ; for(int i = 1; i <= n; ++i) fa[i] = i; for(int i = 0; i < m; ++i) if(!is_bridge[i]) fa[father(edge[i].first)] = father(edge[i].second); for(int i = 1; i <= n; ++i) out[i].clear(); std::set<std::pair<int, int>> edge_fuck; for(int i = 0; i < m; ++i) if(is_bridge[i]) { auto [_f, _t] = edge[i]; int f = father(_f), t = father(_t); if(f > t) std::swap(f, t); if(edge_fuck.find({f, t}) != edge_fuck.end()) continue; edge_fuck.insert({f, t}); out[f].emplace_back(t, i), out[t].emplace_back(f, i); } for(int i = 1; i <= n; ++i) hkr[i].clear(); for(int i = 1; i <= n; ++i) hkr[father(i)].push_back(a[i]); baseline = 0; for(int i = 1; i <= n; ++i) vis[i] = false; for(int i = 1; i <= n; ++i) if(fa[i] == i && !vis[i]) { rst[i] = dfs1(i, i); ans_partial[i] = 0; // std::cerr << "i = " << i << char(10); // for(auto [k, v]: rst[i]) std::cerr << "k, v = " << k << ", " << v << char(10); for(auto [k, v]: rst[i]) if(v > ans_partial[i]) ans_partial[i] = v; baseline += ans_partial[i]; // std::cerr << "ans_partial[" << i << "] = " << ans_partial[i] << char(10); } else rst[i].clear(); // std::cerr << baseline << char(10); // std::cerr << "-------\n"; // return ; for(int i = 1; i <= n; ++i) vis[i] = false; for(int i = 1; i <= n; ++i) if(fa[i] == i && !vis[i]) solve(baseline-ans_partial[i],i); for(int i = 0; i < m; ++i) is_bridge[i] ? std::cout << ans[i] << char(i == m - 1 ? 10 : 32) : std::cout << baseline << char(i == m - 1 ? 10 : 32); return ; } int main() { // freopen("B.in","r",stdin); std::ios::sync_with_stdio(false); int T; std::cin >> T; while(T--) work(); return 0; }

4|0C. Flippy Sequence


分类讨论题,找出两个串不同的极长区间的个数,记为cnt,则:

  • cnt>2,显然无解
  • cnt=2,手玩以下会发现一定恰有6种方法,而且样例给出了这种情况所以很容易理解
  • cnt=1,刚开始被坑了以为和区间长度有关,后面仔细想了下发现就是2×(n1)
  • cnt=0,两次选的区间相同,方案数为n(n+1)2
#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=1e6+5; int t,n; char a[N],b[N]; int main() { for (scanf("%d",&t);t;--t) { RI i; scanf("%d%s%s",&n,a+1,b+1); int cnt=0,lst=0; for (i=1;i<=n;++i) if (a[i]==b[i]) { if (lst+1<=i-1) ++cnt; lst=i; } if (lst+1<=n) ++cnt; if (cnt>2) { puts("0"); continue; } if (cnt==2) { puts("6"); continue; } if (cnt==1) printf("%d\n",2*(n-1)); else printf("%lld\n",1LL*n*(n+1)/2LL); } return 0; }

5|0D. Magic Multiplication


神秘模拟题,细节还是挺多的但想清楚还是很简单的

首先由于没有前导零,因此可以从19枚举a1的取值,当a1确定时我们可以唯一确定出b1bm的值

注意如果某个一位数xa1的倍数,那么对应的bi必须是xa1,否则如果在后面再加一个数得到的bi一定是两位数

否则考虑取出两位来作为a1bi的乘积,但要注意判掉前导零和得到的bi>9的情况

现在我们已经得到了一组b1bm,然后就是用这些值来反着确定a2an的值了

注意当bi=0时对应的ai可以任意取值,因此有一些细节需要注意,最后如果回代检验成功就直接输出

具体细节可以看代码

#include<cstdio> #include<iostream> #include<cstring> #define RI register int #define CI const int& using namespace std; const int N=200005; int t,n,m,a[N],b[N]; char s[N]; int main() { for (scanf("%d",&t);t;--t) { RI i,j; scanf("%d%d%s",&n,&m,s+1); int len=strlen(s+1); bool flag=0; for (a[1]=1;a[1]<=9&&!flag;++a[1]) { bool sign=1; int p=1; for (i=1;i<=m;++i) { if (p>len) { sign=0; break; } if ((s[p]-'0')%a[1]==0) { b[i]=(s[p]-'0')/a[1]; ++p; continue; } if (s[p]=='0') { sign=0; break; } if (p+1>len) { sign=0; break; } if (((s[p]-'0')*10+(s[p+1]-'0'))%a[1]==0) { b[i]=((s[p]-'0')*10+(s[p+1]-'0'))/a[1]; if (b[i]>9) { sign=0; break; } p+=2; continue; } sign=0; break; } for (i=2;i<=n&&sign;++i) { a[i]=-1; for (j=1;j<=m;++j) { if (b[j]!=0) { if (p>len) { sign=0; break; } if ((s[p]-'0')%b[j]==0) { int tmp=(s[p]-'0')/b[j]; if (tmp>9) { sign=0; break; } if (a[i]==-1) a[i]=tmp; else if (a[i]!=tmp) { sign=0; break; } ++p; continue; } if (s[p]=='0') { sign=0; break; } if (p+1>len) { sign=0; break; } if (((s[p]-'0')*10+(s[p+1]-'0'))%b[j]==0) { int tmp=((s[p]-'0')*10+(s[p+1]-'0'))/b[j]; if (tmp>9) { sign=0; break; } if (a[i]==-1) a[i]=tmp; else if (a[i]!=tmp) { sign=0; break; } p+=2; continue; } } else { if (p>len) { sign=0; break; } if (s[p]!='0') { sign=0; break; } ++p; continue; } } if (a[i]==-1) a[i]=0; } if (sign&&p==len+1) { for (i=1;i<=n;++i) putchar(a[i]+'0'); putchar(' '); for (i=1;i<=m;++i) putchar(b[i]+'0'); putchar('\n'); flag=1; } } if (!flag) puts("Impossible"); } return 0; }

6|0E. Plants vs. Zombies


从开局搞到最后发现原来是爆long long了,真是经经又典典啊

首先最小值最大一眼二分答案,然后我们此时可以轻松确定出每个位置需要被走到的最少次数

考虑从前往后贪心处理,我们钦定处理到位置i时所有<i的位置已经满足要求了,那么要刷步数的话就直接在(i,i+1)刷是最优的

总复杂度O(nlogAns),注意潜在的爆long long问题

#include<bits/stdc++.h> using namespace std; #define int long long const int INF = (int)4e18+5; const int N = 1e5+5; int t, n, m, A[N], c[N]; bool check(int val){ // printf("check(%lld)\n", val); for (int i=1; i<=n; ++i){ c[i]=(val+A[i]-1)/A[i]; } __int128 res=0; for (int i=1; i<=n; ++i){ if (i<n || (n==i && c[i]>0)) ++res, --c[i]; if (c[i]>0) c[i+1]-=c[i], res+=2*c[i], c[i]=0; } // printf("res=%lld\n", res); return res<=m; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin >> t; while (t--){ cin >> n >> m; for (int i=1; i<=n; ++i) cin >> A[i]; int L=0, R=INF; while (L<R){ int M = L+(R-L)/2+1; if (check(M)) L=M; else R=M-1; } cout << L << '\n'; } return 0; }

7|0F. Tournament


神秘题,刚开始想漏了一个关键条件卡了我和徐神好久的时间,后面徐神上去写B我在下面冷静后发现好像是个找规律题

大力手玩会发现,当n为奇数时显然无解,2n4nk至多为1,这些都是很trivial的

然后画一画会发现n=8时可以达到n1轮的上界,而n=12时最多就只能做3

索性大力猜测对于数n它能做到的最多轮次就是lowbit(n)1,然后构造方案就直接异或就行

爬上去5min写了一发发现直接过了,我直接呃呃

#include<cstdio> #include<iostream> #define RI register int #define CI const int& using namespace std; const int N=1005; int t,n,k; int main() { for (scanf("%d",&t);t;--t) { RI i,j; scanf("%d%d",&n,&k); auto lowbit=[&](CI x) { return x&-x; }; if (k>lowbit(n)-1) { puts("Impossible"); continue; } for (i=1;i<=k;++i) for (j=0;j<n;++j) printf("%d%c",(j^i)+1," \n"[j==n-1]); } return 0; }

8|0G. Repair the Artwork


看过题人数还是挺可做的一个题,但赛时机时不够了只能作罢,之后有空再补吧


9|0H. Mirror


没一个人过的几何,很符合我对几何的想象


10|0I. Soldier Game


开场就看了这个题,然后一直摸到结束也不会,后面看了眼题解发现就是个傻逼题

首先这类极差问题的经典思路就是枚举确定最小值,然后在只考虑大于阈值的区间时能得到的最大值

这题虽然乍一看数据范围挺大,但仔细一想会发现合法的区间最多只有2n1个,因此可以先排序然后枚举

现在问题就是怎么快速处理在ban掉部分区间的情况下用剩余区间覆盖1n,且最大权值最小

不妨考虑用线段树优化DP,在线段树x(对应区间[l,r])的节点上维护fx,0/1,0/1,表示当覆盖区间[l,r],且覆盖l位置的线段在/不在区间外,覆盖r位置的线段在/不在区间外的情形下,最小化的最大权值

转移的话也很trivial:

fx,p,q=mink{0,1}max(fls(x),p,k,frs(x),k,q)

初始值的设置按照定义推一下也不难,然后每次删除一条线段就直接把线段树上对应的权值改成即可

总复杂度O(nlogn)

#include<cstdio> #include<iostream> #include<algorithm> #include<vector> #include<array> #define int long long #define RI register int #define CI const int& using namespace std; const int N=100005,INF=1e18; int t,n,a[N]; class Segment_Tree { private: int f[N<<2][2][2]; inline void pushup(CI now) { for (RI x=0;x<2;++x) for (RI y=0;y<2;++y) { f[now][x][y]=INF; for (RI k=0;k<2;++k) f[now][x][y]=min(f[now][x][y],max(f[now<<1][x][k],f[now<<1|1][k][y])); } } public: #define TN CI now=1,CI l=1,CI r=n #define LS now<<1,l,mid #define RS now<<1|1,mid+1,r inline void build(TN) { if (l==r) { f[now][0][0]=a[l]; f[now][0][1]=l<n?a[l]+a[l+1]:INF; f[now][1][0]=l>1?-INF:INF; f[now][1][1]=INF; return; } int mid=l+r>>1; build(LS); build(RS); pushup(now); } inline void modify(CI pos,CI len,TN) { if (l==r) { if (len==1) f[now][0][0]=INF; else f[now][0][1]=INF; return; } int mid=l+r>>1; if (pos<=mid) modify(pos,len,LS); else modify(pos,len,RS); pushup(now); } inline int query(void) { return f[1][0][0]; } #undef TN #undef LS #undef RS }SEG; signed main() { for (scanf("%lld",&t);t;--t) { RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&a[i]); vector <array <int,3>> vec; SEG.build(); for (i=1;i<=n;++i) vec.push_back({a[i],i,1}); for (i=1;i<n;++i) vec.push_back({a[i]+a[i+1],i,2}); sort(vec.begin(),vec.end()); int ans=INF; for (auto [val,p,len]:vec) ans=min(ans,SEG.query()-val),SEG.modify(p,len); printf("%lld\n",ans); } return 0; }

11|0J. Books


徐神开场写的,我题面都没看

#include <bits/stdc++.h> using llsi = long long signed int; void work() { int n, m; std::cin >> n >> m; std::vector<llsi> a(n); for(int i = 0; i < n; ++i) std::cin >> a[i]; llsi ans = 0; int c0 = 0; for(int i = 0; i < n; ++i) c0 += !a[i]; if(c0 > m) return void(std::cout << "Impossible\n"); m -= c0; llsi sufmin = 1e17; for(int i = 0; i < n; ++i) if(a[i]) { if(m > 0) { ans += a[i]; m -= 1; } else if(m == 0) { sufmin = std::min(sufmin, a[i] - 1); } } ans += sufmin; if(ans >= 1e17) std::cout << "Richman\n"; else std::cout << ans << char(10); } int main() { std::ios::sync_with_stdio(false); int T; std::cin >> T; while(T--) work(); return 0; }

12|0K. Airdrop


好像也是个可做题,但经典没时间了直接弃疗


13|0L. Sub-cycle Graph


小清新计数题,稍微想一下就会了

首先先特判各种Corner Case,比如m>n时无解;m=n时为(n1)!2(不计方向的圆排列,所以要除以2);m=0时为1

然后考虑一般的情况,不妨设度数为i的点的个数为di,显然我们枚举d1的值后就可以唯一确定d0,d2的值了

考虑现在图的形态,我们首先把编号划分给三种点,方案数为Cnd0×Cnd0d1,然后忽略掉所有的d0

考虑先把d1两两配对作为一条链的两个端点,这个的方案数是个经典问题

式子是Cd1d12×(d12)!×12d12,第一个组合数表示先定下一堆点放左边,然后剩下的点放在右边配对有阶乘种放法,但要注意因为后面加入的度数为2的点在中间的时候已经消除了顺序的影响,因此两端点顺序无关,每个pair都要除以2

最后插入度数为2的点可以这样考虑,先把d2的点任意排列,然后再划分成d12组,每组内元素连续

后者是个经典的插板法例题,因此再乘上d2!×Cd2+d121d12即可

#include <bits/stdc++.h> using llsi = long long signed int; constexpr llsi mod = 1'000'000'007; constexpr llsi ksm(llsi a, llsi b) { llsi res = 1; while(b) { if(b & 1) res = res * a % mod; a = a * a % mod; b >>= 1; } return res; } constexpr llsi inv2 = ksm(2, mod - 2); llsi fac[1000006], facinv[1000006] , inv_pw2[1000006]; void prep_fac(llsi n = 1000000) { fac[0] = 1; for(llsi i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod; facinv[n] = ksm(fac[n], mod - 2); for(llsi i = n; i >= 0; --i) facinv[i - 1] = facinv[i] * i % mod; assert(facinv[0] == 1); inv_pw2[0] = 1; inv_pw2[1] = inv2; for(llsi i = 2; i <= n; ++i) inv_pw2[i] = inv_pw2[i - 1] * inv2 % mod; return ; } inline llsi C(llsi n, llsi m) { if(n < 0 || m < 0 || m > n) return 0; return fac[n] * facinv[m] % mod * facinv[n - m] % mod; } llsi work() { llsi n, m; std::cin >> n >> m; if(m > n) return 0; if(m == n) { if(n <= 2) return 0; return fac[n - 1] * inv2 % mod; } if(m == 0) return 1; llsi ans = 0; for(llsi d1 = 2; d1 <= n; d1 += 2) { const llsi d2 = (2 * m - d1) / 2; const llsi d0 = n - d1 - d2; if(d2 < 0 || d2 > n || d0 < 0 || d0 > n) continue; ans += C(n, d0) * C(n - d0, d1) % mod * ( C(d1, d1 / 2) * fac[d1 / 2] % mod * inv_pw2[d1 / 2] % mod * fac[d2] % mod * C(d2 + d1 / 2 - 1, d1 / 2 - 1) % mod) % mod; } return ans % mod; } int main() { std::ios::sync_with_stdio(false); prep_fac(); int T; std::cin >> T; while(T--) std::cout << work() << char(10); return 0; }

14|0M. Function and Function


祁神开场写的签,我题都没看

#include<bits/stdc++.h> using namespace std; int t, x, k; const int tbl[10] = {1, 0, 0, 0, 1, 0, 1, 0, 2, 1}; int calc(int v){ int res=0; while (v>0){ res += tbl[v%10]; v/=10; } return res; } signed main(){ ios::sync_with_stdio(0); cin.tie(0); cin >> t; while (t--){ cin >> x >> k; while (k>0 && x>0){ --k; x = calc(x); } if (k>0) cout << k%2 << '\n'; else cout << x << '\n'; } return 0; }

15|0Postscript


晚上要上人工智能的实验课,而且明天要补五一的课,晚上有CF都懒得打

唉感觉最近训练量堪忧啊,好久没有打CF/ATC了的说


__EOF__

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