08-09 题解
A
小水题
思路
假设我们选定了当前子序列的绝对众数
为了分出最少的子序列, 肯定要让每个子序列在拥有绝对众数的同时能消化尽量多的其他数字
由此, 可以得到一个贪心策略: 每次取出出现次数最多的一个数字, 消掉出现次数最少的那些数字
这个可以排序后双指针实现, 但是我选择细节更简单的线段树哈哈哈
代码
#include<bits/stdc++.h> #define int long long using namespace std; const int N = 1e6 + 10, INF = 1e18; int n; int a[N], maxi, cnt[N]; bool vis[N]; struct Seg_Tree{ struct Node{ int l, r; int maxi, maxps; int mini, minps; }t[2 * N]; void Push_up(int id){ int l = id * 2, r = id * 2 + 1; if(t[l].maxi > t[r].maxi){ t[id].maxi = t[l].maxi; t[id].maxps = t[l].maxps; }else{ t[id].maxi = t[r].maxi; t[id].maxps = t[r].maxps; } if(t[l].mini < t[r].mini){ t[id].mini = t[l].mini; t[id].minps = t[l].minps; }else{ t[id].mini = t[r].mini; t[id].minps = t[r].minps; } } void Build(int id, int l, int r){ t[id].l = l, t[id].r = r; if(l == r){ if(!cnt[l]){ t[id].maxi = -INF, t[id].maxps = l; t[id].mini = INF, t[id].minps = l; }else{ t[id].maxi = cnt[l], t[id].maxps = l; t[id].mini = cnt[l], t[id].minps = l; } return; } int mid = (l + r) / 2; Build(id * 2, l, mid); Build(id * 2 + 1, mid + 1, r); Push_up(id); } void Modify(int id, int l, int r, int v){ if(r < t[id].l || t[id].r < l){ return; } if(l <= t[id].l && t[id].r <= r){ t[id].maxi += v; t[id].mini += v; if(!t[id].maxi){ t[id].maxi = -INF; t[id].mini = INF; } return; } Modify(id * 2, l, r, v); Modify(id * 2 + 1, l, r, v); Push_up(id); } }tr; signed main(){ cin >> n; for(int i = 1; i <= n; i++){ cin >> a[i]; ++cnt[a[i]]; maxi = a[i] > maxi ? a[i] : maxi; } tr.Build(1, 1, maxi); int cnt = 0; while(true){ int mxi = tr.t[1].maxi; int mxps = tr.t[1].maxps; if(mxi == -INF) break; ++cnt; tr.Modify(1, mxps, mxps, -mxi); int sum = 0, lim = mxi - 1; while(true){ int mni = tr.t[1].mini; int mnps = tr.t[1].minps; if(mni == INF) break; if(sum + mni <= lim){ sum += mni; tr.Modify(1, mnps, mnps, -mni); }else{ int delta = lim - sum; tr.Modify(1, mnps, mnps, -delta); break; } } } cout << cnt << "\n"; }
B
非常巧妙的转化
思路
每个中心点在 上/下、 左/右 的选择可以视作独立的
意思就是在合法(要选择的点没有被占用)的情况下, 上/下 可以任选一个点, 左/右 同理, 相互之间没有影响
然后设计一种建边方式, 表示我们在这两个点(上/下, 左/右)中的选择
在 上和下, 左和右 之间连一条边(如果一方不合法, 那就往自己连边; 如果两方都不合法, 那就无解), 然后给边定向
设有向边
分连通块讨论, 设当前连通块点数为
无解, 因为每条边对应一个出度, 这样每个点不可能只有一个出度 , 此时因为是连通块, 所以一定有 , 是一棵树。 以每个点为根都有一种定向方案, 所以方案数为 , 这时是一颗基环树, 分环和树 考虑。在环上, 有顺时针、 逆时针 种方案(自环只有 种, 特判), 在树上, 每个根节点的入度已经有了, 所以方案固定。 方案数为
完结
代码
其实本题暴搜分连通块考虑, 加上剪枝是可以过的哈哈哈
剪枝代码不放了, 这里是正解
注意并查集大小是 我因为这个挂了 28 pts
#include<bits/stdc++.h> #define int long long #define Pii pair <int, int> using namespace std; const int N = 1e3 + 10, MOD = 1e9 + 7; int n, m; int e[N][N], id[N][N], cid; char s[N]; struct Ufs{ int s[N * N] ,cnte[N * N], si[N * N], tg[N * N]; void Init(int x){ for(int i = 1; i <= x; i++){ s[i] = i; cnte[i] = 0; si[i] = 1; } } int Find(int x){ if(x != s[x]) s[x] = Find(s[x]); return s[x]; } }st; signed main(){ cin >> n >> m; st.Init(n * m); for(int i = 1; i <= n; i++){ cin >> (s + 1); for(int j = 1; j <= m; j++){ e[i][j] = s[j] - '0'; id[i][j] = ++cid; } } for(int i = 1; i <= n; i++){ for(int j = 1; j <= m; j++){ if(e[i][j] == 1){ int fa = 0, fb = 0; if(i != 1 && !e[i - 1][j]){ fa = st.Find(id[i - 1][j]); fb = fa; } if(i != n && !e[i + 1][j]){ fb = st.Find(id[i + 1][j]); if(!fa) fa = fb; } if(fa == fb){ ++st.cnte[fa]; st.tg[fa] = 1; }else{ st.si[fa] += st.si[fb]; st.cnte[fa] += st.cnte[fb] + 1; st.tg[fa] |= st.tg[fb]; st.s[fb] = fa; } if(!fa && !fb){ cout << "0\n"; return 0; } fa = 0, fb = 0; if(j != 1 && !e[i][j - 1]){ fa = st.Find(id[i][j - 1]); fb = fa; } if(j != m && !e[i][j + 1]){ fb = st.Find(id[i][j + 1]); if(!fa) fa = fb; } if(!fa && !fb){ cout << "0\n"; return 0; } if(fa == fb){ ++st.cnte[fa]; st.tg[fa] = 1; }else{ st.si[fa] += st.si[fb]; st.cnte[fa] += st.cnte[fb] + 1; st.tg[fa] |= st.tg[fb]; st.s[fb] = fa; } } } } int ans = 1; for(int i = 1; i <= cid; i++){ if(st.Find(i) == i){ int f = st.Find(i); if(st.si[f] < st.cnte[f]){ cout << "0\n"; return 0; } if(st.si[f] == st.cnte[f]){ if(st.tg[f]){ ans *= 1; }else{ (ans *= 2) %= MOD; } }else{ (ans *= st.si[f]) %= MOD; } } } cout << ans << "\n"; }
C
正解要 CDQ 处理三维偏序, MnZn 写不出来, 这里只介绍结论证明
思路
有一个 一眼看上去就不对 的结论: 如果一个区间
证明:
要证 「若
就能删完」, 只需证 「该区间至少能进行一次删除」, 然后转化为 的子问题 把
分成 段长为 的段 若有一段内满足
那就直接完事了 否则一定有相邻的两段, 一段
, 另一端 , 那一个长度为 的滑动窗口放在上面 因为每次
的变化量为 或 , 所以 的变化函数一定是连续的 根据零点存在性定理(好像是必修一的), 由
到 , 一定会经过 的点, 所以该区间一定能删至少一次
然后简单的
咱也不知道 MX 数据咋造的
代码
部分分的
#include<bits/stdc++.h> using namespace std; const int N = 2e5 + 10, INF = 1e18; int n, a, b; int sa[N], sb[N], dp[N]; char t[N]; signed main(){ cin >> a >> b >> (t + 1); n = strlen(t + 1); for(int i = 1; i <= n; i++){ sa[i] = sa[i - 1]; sb[i] = sb[i - 1]; if(t[i] == '0') ++sa[i]; if(t[i] == '1') ++sb[i]; } int len = a + b; for(int i = 1; i <= n; i++){ dp[i] = dp[i - 1]; for(int k = 1; k * len <= i; k++){ if(sa[i] - sa[i - k * len] <= k * a && sb[i] - sb[i - k * len] <= k * b){ dp[i] = dp[i - k * len] + k > dp[i] ? dp[i - k * len] + k : dp[i]; } } } cout << dp[n] << "\n"; }
D - P9514
思路
要往模
还发现我们的矩阵最大就是
先观察 I, II 类图案的排列方式
I 的就是从初始点开始, 横坐标每隔 d 有一个, 纵坐标每隔 d 有一个(可能表述不严谨, 可以看题面的样例解释), 所以我们的
矩阵包含且仅能包含一个 II 的就是每隔 d 有一列, 每隔 d 有一行
要我们求一个类似最大子矩阵的问题, 复杂度在
先枚举
然后再考虑
对于每一个「不被 I 类图案覆盖、 并且
再考虑扩大
从小往大枚举
代码
#include<bits/stdc++.h> #define int long long #define Pii pair <int, int> using namespace std; const int N = 5e3 + 10, M = 5e5 + 10, INF = 1e9; int n, m, d; int p[M], q[M], r[M], s[M]; int lstps[N][N * 2]; bool visx[N], visy[N]; vector <int> ps[N * 2]; struct List{ int pre[N], nxt[N], len, hd, tl; void Init(){ for(int i = 0; i < d; i++){ pre[i] = i - 1; nxt[i] = i + 1; } hd = 0, tl = d - 1; len = INF; } void Del(int x){ int l = pre[x], r = nxt[x]; if(l != -1) nxt[l] = r; if(r != d) pre[r] = l; if(pre[r] == -1) hd = r; if(nxt[l] == d) tl = l; if(l != -1 && r != d){ len = min(len, d - (r - l) + 1); } } }lst; signed main(){ cin >> n >> m >> d; for(int i = 1; i <= n; i++){ cin >> p[i] >> q[i]; visx[p[i]] = 1; visy[q[i]] = 1; } for(int i = 1; i <= m; i++){ cin >> r[i] >> s[i]; lstps[s[i]][r[i]] = r[i]; lstps[s[i]][r[i] + d] = r[i] + d; } for(int i = 0; i < d; i++){ for(int j = 1; j < 2 * d; j++){ lstps[i][j] = max(lstps[i][j], lstps[i][j - 1]); } } int ans = d * d; for(int l = 0; l <= d; l++){ int r = 0; for(int j = l + d - 1; j >= 0; j--){ if(visx[j % d]){ r = j; break; } } lst.Init(); for(int y = 0; y < d; y++){ if(!visy[y]){ if(lstps[y][l + d - 1] < r){ lst.Del(y); }else{ ps[lstps[y][l + d - 1]].push_back(y); } } } for(int j = r; j <= l + d - 1; j++){ for(auto k : ps[j]){ lst.Del(k); } ans = min(ans, (j - l + 1) * min(lst.len, (lst.tl - lst.hd + 1))); ps[j].clear(); } } cout << ans << "\n"; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话