2024 ICPC National Invitational Collegiate Programming Contest, Wuhan Site
2024 ICPC National Invitational Collegiate Programming Contest, Wuhan Site
I. Cyclic Apple Strings
题意:给定一个01字符串,每次操作可以将这个字符串向左循环移动任意次数,求让这个字符串变成有序的需要最少几次操作
思路:每次只能减少最右边的不和有边界相邻的一个1的长块,每次删去最右边的即可
void solve(){ string s; cin >> s; int ans = 0, idx = s.size() - 1; while(idx >= 0 && s[idx] == '1') idx --; for(idx; idx >= 0; idx --){ if(s[idx] == '1' && s[idx + 1] == '0') ans ++; } cout << ans << '\n'; }
K. Party Games
题意:给定 n 个整数从左到右排成一列,每次可以从两端拿走其中一个数字,如果数字被拿完了或者剩余的数字的异或和为0,则当前操作无法进行,就失败了
思路:打表发现 n % 4 == 0 || n % 4 == 1 的情况下 Fluttershy 必胜,否则必败
void solve(){ ll n; cin >> n; n %= 4; if(n == 1 || n == 0) cout << "Fluttershy\n"; else cout << "Pinkie Pie\n"; }
B. Countless Me
题意:给定一个数组,每个数组的值可以随便分配,求最后的最小或运算的和
思路:从高位往低位贪心即可,如果在当前位的后面放满而且不够,那么放在当前位是最好的选择,注意这里的位数不要太大
void solve(){ ll n, x, sum = 0; cin >> n; for(int i = 1; i <= n; i ++){ cin >> x; sum += x; } ll ans = 0; for(int i = 30; i >= 0; i --){ if(sum > ((1ll << i) - 1) * n){ ll num = min(n, sum / (1ll << i)); sum -= num * (1ll << i); ans += 1ll << i; } } cout << ans << '\n'; }
F. Custom-Made Clothes
题意:给定一个n * n 的矩阵,矩阵的每个点大于等于它左边的和上边的点(如果存在),每次可以查询一个点的数字是不是小于等于给定的询问值,在50000次查询中得出第k大的数字
思路:看50000次查询能猜到复杂度应该是2nlogn级别,二分答案,每次查询从左下角开始,如果小于等于mid,往上移动,否则把这一列上面的数字的个数全部加上,否则左移,每次查询的次数不超过2*n,总计2nlogn次查询得出答案
int n, k; int ask(int x, int y, int z){ cout << "? " << x << ' ' << y << ' ' << z << endl; int ans; cin >> ans; return ans; } bool check(int x){ int sum = 0; int a = n, b = 1; while(a >= 1 && b <= n){ int ans = ask(a, b, x); if(ans == 1){ sum += a; b ++; } else{ a --; } } if(sum >= k) return true; else return false; } void solve(){ cin >> n >> k; k = n * n - k + 1; int l = 1, r = n * n; while(l < r){ int mid = l + r >> 1; if(check(mid)) r = mid; else l = mid + 1; } cout << "! " << l << endl; }
D. ICPC
题意:有 n 个点,每个点的价值都是 ai 并且每个点被拿走之后价值变为0,求从每个点开始移动次数为 1~2 * n 能获得的最大价值
思路:很容易想到,从每个点出发之后最多折返一次,预处理出价值的前缀和,方便求前缀价值和,然后求出不折返的情况下能获得的最大价值,然后求出从每个点的左边折返过来的最大价值,同理求出右边,最后取max即可
ll n, a[N]; ll dp1[N][N * 2], dp2[N][N * 2]; void solve(){ cin >> n; for(int i = 1; i <= n; i ++) cin >> a[i]; for(int i = 1; i <= n; i ++) a[i] += a[i - 1]; for(int i = 1; i <= n; i ++){ for(int j = 1; j <= n * 2; j ++){ int l = max(1, i - j), r = min(n, 1ll * i + j); dp1[i][j] = max(a[i] - a[l - 1], a[r] - a[i - 1]); } }//不折返的情况 for(int i = 1; i <= n; i ++){ for(int j = 1; j <= 2 * n; j ++){ dp2[i][j] = max(dp2[i - 1][j - 1], dp1[i][j]); } } for(int i = n; i >= 1; i --){ for(int j = 1; j <= n * 2; j ++){ dp1[i][j] = max(dp1[i][j], dp1[i + 1][j - 1]); } } ll ans = 0; for(int i = 1; i <= n; i ++){ ll res = 0; for(int j = 1; j <= 2 * n; j ++){ res ^= j * max(dp1[i][j], dp2[i][j]); } ans ^= (i + res); } cout << ans << '\n'; }
E. Boomerang
题意:有一棵树,开始时假消息从r点开始传播,每次从已传播到的点开始传播到相邻的点,云宝从t0开始辟谣,辟谣从ro点开始,每次从已辟谣的点传播到相邻的k个所有节点,求k为1~n的情况下每次任选r0的情况最早什么时候完成辟谣
思路:可以很容易发现,从直径中间开始辟谣是最好的,动态维护一个树直径,LCA即可,每次把下一层的点加入,当加入一个新的点的时候,直径只有三种情况,假设新加入的点是 x,原直径两端是u, v,那么新的直径只会是(u, v), (u, x), (v. x), 不断扩大树的直径,然后记录每一秒的树的直径,然后开始枚举秒数,最大不会超过n + t0,因为时间是随着 k 的增大逐渐递减,所以枚举秒数,当就理论上扩散的深度比直径/2大,ans--,否则输出ans
struct LCA{ int n; vector<int> dep, vis; vector<vector<int>> fa; LCA(int _n) : n(_n), dep(_n + 10), vis(_n + 10), fa(n + 1, vector<int>(25, 0)) {} void init(auto edge[], int x){ auto dfs =[&](auto && dfs, int u, int fath){ if(vis[u]) return; vis[u] = 1; dep[u] = dep[fath] + 1; fa[u][0] = fath; for(int i = 1; i <= __lg(dep[u]); i ++){ fa[u][i] = fa[fa[u][i - 1]][i - 1]; } for(auto v : edge[u]){ dfs(dfs, v, u); } }; dfs(dfs, x, 0); } int query(int a, int b){ if(dep[a] > dep[b]) swap(a, b); while(dep[a] != dep[b]) b = fa[b][__lg(dep[b] - dep[a])]; if(a == b) return a; for(int k = __lg(dep[a]); k >= 0; k --){ if(fa[a][k] != fa[b][k]){ a = fa[a][k], b = fa[b][k]; } } return fa[a][0]; } int dist(int u, int v){ return dep[u] + dep[v] - 2 * dep[query(u, v)]; } }; void solve(){ int n; cin >> n; vector<int> edge[n + 1]; for(int i = 1; i < n; i ++){ int u, v; cin >> u >> v; edge[u].push_back(v); edge[v].push_back(u); } int r, t0; cin >> r >> t0; LCA lca(n); lca.init(edge, r); vector<array<int, 3>> dd(n + 1); dd[1] = {1, r, r};//d, u, v int d = 0, du = r, dv = r; queue<PII> q; q.push({1, r}); while(q.size()){ auto [deep, u] = q.front(); q.pop(); auto now = max(dd[deep], dd[deep - 1]); int ud = lca.dist(now[1], u) + 1, vd = lca.dist(now[2], u) + 1; if(ud > now[0]){ now = {ud, u, now[1]}; } if(vd > now[0]){ now = {vd, u, now[2]}; } dd[deep] = max(dd[deep], now); for(auto v : edge[u]){ if(v == lca.fa[u][0]) continue; q.push({deep + 1, v}); } } for(int i = 1; i <= n; i ++) dd[i][0] = max(dd[i][0], dd[i - 1][0]); int ans = n + t0; for(int i = 1; i <= n; i ++){ while(dd[min(n, ans)][0] / 2 <= (ans - 1 - t0) * i){ ans --; } cout << ans << ' '; } }
M. Merge
题意:给定n个数字,每次可以把绝对值差为1的两个数字合并,求最终数字任意排序后最大字典序情况
思路:由于是求字典序,肯定优先得到最大的数字,由于是绝对值差为1的两个数字的合并,所以一个为奇数,一个为偶数,每次取出最大的偶数合并,要不和x + 1合并,要不和x - 1合并,递归的取合并数字,如果需要的数字已经存在了,那就优先合成进去,然后再用小数字合成,可以发现, 奇数可以用一个奇数一个偶数合成,偶数除非本身就存在,否则无法合成,用map记录数字的数量来递归的合成即可
void solve(){ ll n; cin >> n; map<ll, ll> mp; multiset<ll> even; for(int i = 1; i <= n; i ++){ ll x; cin >> x; mp[x] ++; if(x % 2 == 0) even.insert(x); } auto check =[&](auto && check, ll x) -> bool{ if(mp[x]){ mp[x] --; return true; } ll x2 = x / 2, y2 = x2 + 1; if(y2 % 2 == 0) swap(x2, y2); if(!mp[x2]) return false; else{ mp[x2] --; if(check(check, y2)){ return true; } else{ mp[x2] ++; return false; } } }; vector<ll> ans; while(even.size()){ auto x = *even.rbegin(); even.erase(even.find(x)); if(!mp[x]) continue; mp[x] --; if(check(check, x + 1)){ mp[x * 2 + 1] ++; } else if(check(check, x - 1)){ mp[x * 2 - 1] ++; } else{ ans.push_back(x); } } for(auto &[x, y] : mp){ for(int i = 1; i <= y; i ++) ans.push_back(x); } cout << ans.size() << '\n'; sort(ans.begin(), ans.end(), greater<ll>()); for(auto x : ans) cout << x << ' '; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话