The 2024 CCPC Online Contest
写在前面
补题地址:https://codeforces.com/gym/105336
以下按个人向难度排序。
唉唉唐吧真是
我去居然还能打出名额哈哈真牛逼
L
签到。
K
签到。
dztlb 大神直接秒了。
显然奇数先手取 1 必胜,则偶数时为了让自己不输,先手和后手均会保证在取到 2 之前,取的数和沙堆的大小一直都是偶数,否则会让对方必胜。
于是一个显然的想法是考虑 ,发现当 时,仅需先手取 然后模仿后手操作即可,否则一次操作后 ,则后手必胜。
复制复制#include <bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long int T,n,k; int main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); cin>>T; while(T--){ bool fl=0; cin>>n>>k; int tt=1; while(tt<=k){ if(n%tt==0){ if((n/tt)%2==1) fl=1; } tt*=2; } if(fl) puts("Alice"); else puts("Bob"); } return 0; }
B
枚举,结论。
一个显然的发现是有序(升序或降序)数列代价最小,可以考虑调整法证明,当某两位置不有序时,一定会对包含这两个位置的一些区间造成更多的代价。于是仅需考虑重复元素之间的顺序的贡献即可。
注意特判全相等时升序降序是等价的。
#include <bits/stdc++.h> using namespace std; #define LL long long #define ull unsigned long long const int kN = 1e6 + 10; const LL p = 998244353; int n, a[kN], cnt[kN], num; LL fac[kN]; int main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); std::cin >> n; for (int i = 1; i <= n; ++ i) std::cin >> a[i]; fac[1] = 1; for (int i = 2; i <= n; ++ i) fac[i] = fac[i - 1] * i % p; std::sort(a + 1, a + n + 1); LL ans1 = 0; for (int l = 1; l <= n; ++ l) { for (int r = l; r <= n; ++ r) { ans1 += 1ll * (a[r] - a[l]); } } for (int i = 1; i <= n; ++ i) { ++ cnt[a[i]]; if (cnt[a[i]] == 1) ++ num; } LL ans2 = (num == 1 ? 1 : 2); for (int i = 1; i <= n; ++ i) { if (cnt[a[i]] == 0) continue; ans2 = ans2 * fac[cnt[a[i]]] % p; cnt[a[i]] = 0; } std::cout << ans1 << " " << ans2 % p; return 0; }
D
DP。
显然的 DP,发现构造字符串的方案实际构成一个完全二叉树的结构,于是仅需考虑在构造这棵树的过程中,统计完全位于两棵子树中的字符串数量,以及跨越根节点的字符串数量即可。
于是可以设一个显然的状态 表示对于前缀 中,子串 的出现次数,转移时讨论跨越子树的字符串中,根节点是否有贡献即可。详见代码。
总时间复杂度 级别。
#include <bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long const int kN = 110; const ll p = 998244353; ll f[kN][kN][kN], pw2[kN]; int n, m; string s, t; int main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); std::cin >> s >> t; n = s.length(), m = t.length(); s = "$" + s, t = "$" + t; pw2[0] = 1; for (int i = 1; i <= 10; ++ i) pw2[i] = pw2[i - 1] * 2; for (int i = 1; i <= n; ++ i) { for (int l = 1; l <= m + 1; ++ l) { for (int r = 0; r < l; ++ r) { f[i - 1][l][r] = 1; } } for (int l = 1; l <= m; ++ l) { for (int r = l; r <= m; ++ r) { int len = r - l + 1; if (i <= 8 && pw2[i] < len) break; f[i][l][r] = 2ll * f[i - 1][l][r] % p; for (int j = l; j < r; ++ j) { f[i][l][r] += 1ll * f[i - 1][l][j] * f[i - 1][j + 1][r] % p; f[i][l][r] %= p; } for (int j = l; j <= r; ++ j) { if (s[i] != t[j]) continue; f[i][l][r] += 1ll * f[i - 1][l][j - 1] * f[i - 1][j + 1][r] % p; f[i][l][r] %= p; } } } } cout << f[n][1][m]; return 0; } /* aaaaaa a abc abca */
E
期望。
呃呃这题场上三个人看了 4h 中间过了三道题没做出来真唐吧
最大数量即尽量使所有字符串没有公共前缀,即尽可能使每一层的节点均填满。则答案即为:
考虑期望。插入的每个字符串长度均为 ,则每个字符串都会占据 层的一个节点。即对于建出来的字典树,第 层上的所有节点,一定是对应了这 个字符串的长度为 的前缀。又字符串随机构造,则相当于在第 层上随机选择了 个节点,并求随机选择节点的数量。
考虑每一个节点对这一层的贡献再乘上本层总数即为本层贡献。考虑反面,求该节点没有被选择的概率(相当于限制仅能从 个节点中独立地随便选 个),则可知期望的总数为:
#include <bits/stdc++.h> using namespace std; #define LL long long #define ull unsigned long long const int kN = 1e6 + 10; const LL p = 998244353; LL n, m; LL qpow(LL x_, LL y_) { LL ret = 1; while (y_) { if (y_ & 1ll) ret = ret * x_ % p; x_ = x_ * x_ % p, y_ >>= 1ll; } return ret; } int main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); std::cin >> n >> m; LL ans = 0; for (int i = 0; i <= m; ++ i) { if (i <= 3 && qpow(26, i) <= n) ans += qpow(26, i); else ans += n; } cout << ans % p << " "; LL sum = 0, inv = qpow(26, p - 2), invpw = 1, pw = 1; for (int i = 0; i <= m; ++ i) { LL s = (1 - qpow(1 - invpw + p, n) + p) % p * pw % p; invpw = invpw * inv % p, pw = pw * 26 % p; sum += s, sum %= p; } cout << sum; return 0; }
J
异或,线性基
感觉线性基裸题啊呃呃
考虑令 ,并预处理 。由异或的性质,则交换 对 的影响等价于令两者均异或上 。问题变为可以任意异或 ,求 。
考虑从高位到低按位贪心,设当前枚举到第 位,且有 ,另一方为 :
- 若 该位均为 0,或仅有 该位为 0,则异或后不会使答案更优,跳过;
- 若 该位为 1,则考虑尽量将这一位消除,且不影响已确定的更高的位的答案。
由上述不影响更高位的要求,想到考虑对 维护线性基,然后直接在上述贪心过程中使用对应的向量消去答案中某位的 1 即可。
#include <bits/stdc++.h> using namespace std; #define int long long #define ull unsigned long long const int kN = 1e6 + 10; int n, a[kN], b[kN], suma, sumb, c[kN]; int ans, p[70]; void insert(int c_) { for (int i = 60; ~i; -- i) { if (!(c_ >> i)) continue; if (!p[i]) { p[i] = c_; break; } c_ ^= p[i]; } } void solve(int sa_, int sb_) { int x = sa_, y = sb_; for (int i = 60; ~i; -- i) { if (x < y) std::swap(x, y); if (!(x >> i & 1) && !(y >> i & 1)) continue; if ((x >> i & 1) && (y >> i & 1)) { x ^= p[i], y ^= p[i]; } else if ((x >> i & 1) && x >= y) { x ^= p[i], y ^= p[i]; } else { continue; } } ans = min(ans, max(x, y)); } signed main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); int T; std::cin >> T; while (T --) { std::cin >> n; suma = sumb = 0; for (int i = 1; i <= n; ++ i) std::cin >> a[i], suma ^= a[i]; for (int i = 1; i <= n; ++ i) std::cin >> b[i], sumb ^= b[i]; for (int i = 0; i <= 60; ++ i) p[i] = 0; for (int i = 1; i <= n; ++ i) c[i] = a[i] ^ b[i], insert(c[i]); ans = max(suma, sumb); solve(suma, sumb); cout << ans << "\n"; } return 0; } /* 1 2 2147483646 2147483647 1 2147483647 */
I
DP。
dztlb 大神写的,看起来是简单题,就不补了。
code by dztlb:
#include <bits/stdc++.h> using namespace std; #define int long long #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 int N=505; const int mod=998244353; int n,m; int a[N],b[N]; int sum[N]; int f[N][N]; int dp[N]; void solve(int k){ memset(f,0,sizeof(f)); sum[0]=0; for(int i=0;i<=m;++i) f[i][0]=1; for(int i=1;i<=m;++i){ for(int j=1;j<=min(i, n);++j){ if(j>sum[max(b[i]-k,0ll)]) break; f[i][j]=f[i-1][j]+f[i-1][j-1]*max(sum[max(b[i]-k,0ll)]-j+1,0ll)%mod; f[i][j]%=mod; } } for(int i=1;i<=min(n, m);++i){ dp[k]+=f[m][i]; dp[k]%=mod; } } signed main() { // ios::sync_with_stdio(false); // cin.tie(0), cout.tie(0); cin>>n>>m; for(int i=1;i<=n;++i) { cin>>a[i]; sum[a[i]]++; } for(int i=1;i<=m;++i) cin>>b[i]; sort(b+1,b+1+m); sort(a+1,a+1+n); int maxx=0; int minn=1000; for(int i=1;i<=m;++i){ for(int j=b[i]-1;j>=1;--j){ if(sum[j]) minn=min(minn,b[i]-j),maxx=max(maxx,b[i]-j); } } if(minn==1000||maxx==0){ puts("0"); return 0; } for(int i=1;i<=500;++i) sum[i]+=sum[i-1]; // minn=1; // cout<<minn<<' '<<maxx<<endl; for(int k=minn;k<=maxx;++k){ solve(k); } int ans=dp[maxx]*maxx%mod; for(int i=minn;i<maxx;i++){ ans+=(dp[i]-dp[i+1]+mod)%mod*(i)%mod; ans%=mod; } cout<<ans<<endl; return 0; } /* 5 5 1 2 3 4 5 2 3 4 5 6 */
G
网络流
显然应当让第一个人尽量在他要吃的所有菜都买单,于是可以直接计算出第一个人花的钱的上界,其他人花的钱的上界一定是第一个人的上界减 1,问能否在所有上界满足情况下能买所有菜。
仅有上界则是显然的最大流问题,建图求最大流检查是否等于所有菜的总价即可。
注意建边时的实际意义一定要符合真实情况!
//知识点: /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 1e5 + 10; const int kM = 2e6 + 10; const LL kInf = 1e18 + 2077; //============================================================= int n, m, S, T, a[kN], car[kN]; int x[kN], y[kN], dish[kN]; int nodenum, maxnodenum, edgenum = 1, v[kM], ne[kM], head[kN]; int cur[kN], dep[kN]; LL w[kM]; LL flag, maxa, maxflow; //============================================================= void addedge(int u_, int v_, LL w_) { v[++ edgenum] = v_; w[edgenum] = w_; ne[edgenum] = head[u_]; head[u_] = edgenum; v[++ edgenum] = u_; w[edgenum] = 0; ne[edgenum] = head[v_]; head[v_] = edgenum; } void init() { std::cin >> n >> m; edgenum = 1; nodenum = n + m; maxnodenum = n + m + 2; S = ++ nodenum, T = ++ nodenum; for (int i = 1; i <= maxnodenum; ++ i) head[i] = 0; for (int i = 1; i <= n; ++ i) { std::cin >> a[i] >> car[i]; } maxa += car[1]; for (int i = 1; i <= m; ++ i) { std::cin >> x[i] >> y[i] >> dish[i]; if (x[i] == 1 || y[i] == 1) maxa = std::min(1ll * a[1], maxa + dish[i]); } addedge(S, 1, maxa - car[1]); for (int i = 2; i <= n; ++ i) { if (car[i] >= maxa) flag = 1; LL w_ = std::max(0ll, maxa - car[i] - 1); w_ = std::min(std::max(0ll, 1ll * a[i] - car[i]), w_); addedge(S, i, w_); } for (int i = 1; i <= m; ++ i) { addedge(x[i], i + n, kInf); addedge(y[i], i + n, kInf); addedge(i + n, T, dish[i]); maxflow += dish[i]; } } bool bfs() { std::queue <int> q; memset(dep, 0, (nodenum + 1) * sizeof (int)); dep[S] = 1; //注意初始化 q.push(S); while (!q.empty()) { int u_ = q.front(); q.pop(); for (int i = head[u_]; i > 1; i = ne[i]) { int v_ = v[i], w_ = w[i]; if (w_ > 0 && !dep[v_]) { dep[v_] = dep[u_] + 1; q.push(v_); } } } return dep[T]; } LL dfs1(int u_, LL into_) { if (u_ == T) return into_; int ret = 0; for (int i = cur[u_]; i > 1 && into_; i = ne[i]) { int v_ = v[i]; LL w_ = w[i]; if (w_ && dep[v_] == dep[u_] + 1) { LL dist = dfs1(v_, std::min(into_, w_)); if (!dist) dep[v_] = kN; into_ -= dist; ret += dist; w[i] -= dist, w[i ^ 1] += dist; if (!into_) return ret; } } if (!ret) dep[u_] = 0; return ret; } LL dinic() { LL ret = 0; while (bfs()) { memcpy(cur, head, (nodenum + 1) * sizeof (int)); ret += dfs1(S, kInf); } return ret; } //============================================================= int main() { // freopen("1.txt", "r", stdin); std::ios::sync_with_stdio(0), std::cin.tie(0); init(); if (flag) { std::cout << "NO\n"; return 0; } std::cout << ((dinic() == maxflow) ? "YES" : "NO") << "\n"; return 0; } /* 4 3 1000 900 1000 900 1000 900 1000 900 1 2 100 1 3 100 1 4 100 3 1 100 50 70 50 100 48 3 3 1 */
C
贪心,DP。
场上想出来那个树形 DP 但是觉得讨论起来会很麻烦就没写。
写在最后
学到了什么:
- E:每个元素均等概率情况,考虑单个元素出现在答案中的期望概率,然后乘上元素数量即为总贡献。
- G:注意实际意义!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战