这个网站是 雨沐ネブラ (Amekai_nebula) 的个人博客网站
未来这个网站会更新若干内容,包括不局限于题解、感悟等内容
1001-湖上午后
题意: 给出两个非负整数 和 , 求出方程 在 都取 中整数时一共有多少组解。答案对 998244353 取模。
有一个经典的等式是 , 知道了这个之后可以记
这样两个式子组合就是 。最后只有 或者 两种解。但是只需要求一种就行,另外一部分可以直接乘2得到
假设这里是 , 的二进制为1的位就必须是 的子集,这里就要用到数位dp了,定义 dp[i][j][k][r] (i : 当前是从高到低第几位,j : 是否紧贴最小值,k : 是否紧贴最大值,r : 是否有 a < b) 这个dp第一维度可以压掉,所以后面代码中下标 代表这里的 ,剩下的一次对应。初始值也就是紧贴上下界,没有 关系 dp[1][1][0] = 1
之前有提到子集的关系,所以 的情况只有三种 {0, 0}, {0, 1}, {1, 1}。这里转移只说一下 的变化,其他也是类似的。如果 说明此时挨着下界,所以 的这一位 不能小于 if(i == 1 && ai < si) continue; 如果当前依然有 那么 就还是 1 ,否则就变化为0 int ii = (i == 1 && ai == si) ? 1 : 0;
你可能会好奇为什么会有一个 用来表示 。但这其实是题目要求是这个,我们已有的限制只能保证
详细转移看下面代码,数位dp一般都从最高位开始,方便确定大小关系。数位dp还是太难写了!
void solve(){ string s, t; cin >> s >> t; int tt = 0, ss = 0; for(char c : s) { ss = (ss * 2 + c - '0') % mod; } for(char c : t) { tt = (tt * 2 + c - '0') % mod; } int ans = (tt - ss + 1 + mod) % mod;
if (s.size() < t.size()) { s = string(t.size() - s.size(), '0') + s; } else if (t.size() < s.size()) { t = string(s.size() - t.size(), '0') + t; } int n = s.length();
int dp[2][2][2] = {0}; vector<pii> ch = {{0, 0}, {0, 1}, {1, 1}}; dp[1][1][0] = 1; fore(_, 0, n) { int si = s[_] - '0', ti = t[_] - '0'; int ndp[2][2][2] = {0}; fore(i, 0, 2) fore(j, 0, 2) fore(k, 0, 2) { for(auto[ai, bi] : ch) { if(i == 1 && ai < si) continue; int ii = (i == 1 && ai == si) ? 1 : 0;
if(j == 1 && bi > ti) continue; int jj = (j == 1 && bi == ti) ? 1 : 0;
int kk = (k == 0 && ai == bi) ? 0 : 1; ndp[ii][jj][kk] += dp[i][j][k]; ndp[ii][jj][kk] %= mod; } } memcpy(dp, ndp, sizeof(dp)); } int S = 0; fore(i, 0, 2) fore(j, 0, 2) { S = (S + dp[i][j][1]) % mod; } S = S * 2 % mod; ans = (ans + S) % mod; cout << ans << endl;}1002-庭扫落樱
题意: 在坐标系的第一象限有 个激光发射器,每个位于 每个都有一个方向 分表代表上右下左。输出最少放置多少个障碍物可以阻挡所有射线,保证不会有两个发射器互相照射,障碍物可以放在发射器那个点上
容易发现有些发射器不用单独去管,如果两个发射器方向相同并且一个发射器光线在另一个的光线上,后面那个发射器就可以排除开
这样排除一些之后最少障碍物数量 = 总数 - 重合的点数
尽量省就要一个障碍物覆盖多个光线,这里删掉一些后最多就是覆盖两个,这就有点像二分图了,上下和左右作为二分图左侧和右侧,两个光线有交点就把他们连线。结果就是总数减去最大匹配数
class BP {public: vector<int> vis, link; vector<vector<int>> p; int n, m; BP(int n, int m) : n(n), m(m) { p.resize(n + 1); vis.resize(m + 1, false), link.resize(m + 1, -1); } inline void add(int u, int v) { p[u].emplace_back(v); }; bool dfs(int s) { for(int to : p[s]) { if(vis[to]) continue; vis[to] = true; if(link[to] == -1 || dfs(link[to])) { link[to] = s; return true; } } return false; } int match() { int ans = 0; fore(i, 1, n + 1) { fill(vis.begin() + 1, vis.end(), false); if(dfs(i)) ans++; } return ans; }};
void solve(){ int n; cin >> n; map<int, int> mp1, mp2, mp3, mp4; vector<pii> up, down, right, left; int x, y, d; fore(i, 1, n + 1) { cin >> x >> y >> d; if(d == 0) { if(!mp1.count(x)) mp1[x] = y; else mp1[x] = max(mp1[x], y); }else if(d == 1) { if(!mp2.count(y)) mp2[y] = x; else mp2[y] = max(mp2[y], x); }else if(d == 2) { if(!mp3.count(x)) mp3[x] = y; else mp3[x] = min(mp3[x], y); }else { if(!mp4.count(y)) mp4[y] = x; else mp4[y] = min(mp4[y], x); } } for(auto[x, y] : mp1) up.emplace_back(x, y); for(auto[x, y] : mp3) down.emplace_back(x, y); for(auto[y, x] : mp2) right.emplace_back(x, y); for(auto[y, x] : mp4) left.emplace_back(x, y);
BP bp(up.size() + down.size(), right.size() + left.size()); int l = 0; for(int i = 0; i < up.size(); i++) { l++; x = up[i].first, y = up[i].second; int r = 0; for(auto[xx, yy] : right) { r++; if(xx <= x && yy >= y) bp.add(l, r); } for(auto[xx, yy] : left) { r++; if(xx >= x && yy >= y) bp.add(l, r); } } for(int i = 0; i < down.size(); i++) { l++; x = down[i].first, y = down[i].second; int r = 0; for(auto[xx, yy] : right) { r++; if(xx <= x && yy <= y) bp.add(l, r); } for(auto[xx, yy] : left) { r++; if(xx >= x && yy <= y) bp.add(l, r); } } cout << up.size() + down.size() + right.size() + left.size() - bp.match() << endl;}1003-竹林清韵
题意: 给出一个有 个数的数组,铃仙每次可以选取任意一个数,因幡帝会固定选择数组的前 个数,对所有的 最大化铃仙所选数的和
题目条件翻译一下就是数组的前 个数中最多只能选择 个数。如果 是固定的,那这个直接用小根堆即可,每次碰到新的数都尝试加入堆中,如果容量超出可选数的限制就 pop 掉一个,最后统计和即可
但这里是所有的 这么做就是 肯定不行了,关键是从前面开始这个至多选几个很处理,从后开始往往会得到比较好的性质: 假设最后总共要选 个数, 从 到 至少选 个数,这个至少选多少个就容易处理的多,因为这里已经选择出来的数之后不会被替代了,之前已经选的是可能被后面更大的数更换的
每次只需要知道某个区间最大值即可,这个可以用线段树维护,当某个数被选择时就将其替换为最小值就等同删除了。总共选择的数的个数是一个调和级数,每次查询修改是 最终复杂度是
class SegTree{private: int n; vector<int> a; vector<pii> info; pii bet(pii x, pii y) { if(x.first != y.first) return x.first > y.first ? x : y; return x.second < y.second ? x : y; }
void build(int i, int l, int r) { if(l == r) info[i] = {a[l], l}; else { int mid = (l + r) >> 1; build(i << 1, l, mid); build(i << 1 | 1, mid + 1, r); info[i] = bet(info[i << 1], info[i << 1 | 1]); } } void set(int pos, int val, int l, int r, int i) { if(pos < 1 || pos > n) return; if(l == r) { a[pos] = val; info[i] = {val, pos}; return; } int mid = (l + r) >> 1; if(pos <= mid) set(pos, val, l, mid, i << 1); else set(pos, val, mid + 1, r, i << 1 | 1); info[i] = bet(info[i << 1], info[i << 1 | 1]); } pii query(int jobl, int jobr, int l, int r, int i) { if(jobl <= l && r <= jobr) return info[i]; int mid = (l + r) >> 1; pii ans = {-1, -1}; if(jobl <= mid) ans = bet(query(jobl, jobr, l, mid, i << 1), ans); if(jobr > mid) ans = bet(query(jobl, jobr, mid + 1, r, i << 1 | 1), ans); return ans; }public: SegTree(int n, vector<int>& arr) : n(n), info((n << 2) + 1), a(n + 1) { fore(i, 1, n + 1) a[i] = arr[i]; build(1, 1, n); } void set(int pos, int val) { set(pos, val, 1, n, 1);} pii query(int jobl, int jobr) {return query(jobl, jobr, 1, n, 1);} int get(int pos) {return a[pos];}};
void solve(){ int n; cin >> n; vector<int> val(n + 1); fore(i, 1, n + 1) cin >> val[i]; SegTree seg(n, val); for(int len = 1; len <= n - 1; len++) { int ans = 0; vector<int> used; for(int k = n / (len + 1); k >= 0; k--) { if(k * (len + 1) + 1 > n) continue; auto[maxn, pos] = seg.query(k * (len + 1) + 1, n); ans += maxn; used.emplace_back(pos); seg.set(pos, -inf); } for(int c : used) seg.set(c, val[c]); cout << ans << ' '; } cout << endl;}1004-三途川畔
题意: 给定两个非负整数 和 ,请你构造 个正整数,使得这些数的乘积大于或等于它们的和,并且两者的差恰好等于
合乘积有关那就是1越多越好,所以假设有 个 1,剩下一下 。所以目前要满足的关系就是
一定要化简!化简得到 显然 时 一定有解 构造完毕
void solve(){ int n, k; cin >> n >> k; fore(i, 1, n - 2 + 1) cout << 1 << ' '; cout << 2 << ' ' << n + k << endl;}1007-云端航迹
题意: 给出一个有 个数的数组,Alice和Bob轮流在任意一个子数组中玩游戏,Alice先手,每次可以选择任意一个数删除,任何时候集合中所有数的异或和为0,则最后一次操作数组的人输掉游戏,问在所有的子数组中两人分别获胜的次数
Alice 先手想要赢子数组大小必须是偶数,(如果是奇数,对手面对的就是偶数,此时一直总的异或和是 S 且不为 0 ,对方只需要在这个数组中选一个数满足 即可一直拖下去,直到最后Alice选完最后一个数。并且这个事对手一定做得到,假设所有位置的值都是 S ,又因为元素个数是偶数,出现这种Alice已经输了,所以对手一定找得到这个数)。
其次开始这个异或和不是0,这里同样和上面一样思考,我要找一个 ,但是是一定找得到的,可以一直拖到对手走到必败态或者最后全部取完
具体实现就是维护前缀异或和,对左边按奇偶分类,并且用cnt数组记录所有前缀中为某个值的位置有多少个
void solve(){ int n; cin >> n; vector<int> val(n + 1); vector<int> pre(n + 1); fore(i, 1, n + 1) { cin >> val[i]; pre[i] = pre[i - 1] ^ val[i]; } vector<int> cnt1(4e5 + 5), cnt2(4e5 + 5); int even = 0, odd = 0; int ans = 0; fore(i, 0, n + 1) { if(i & 1) { ans += odd; odd += 1; ans -= cnt1[pre[i]]; cnt1[pre[i]]++; }else { ans += even; even += 1; ans -= cnt2[pre[i]]; cnt2[pre[i]]++; } } cout << ans << ' ' << (n + 1) * n / 2 - ans << endl;}1009-微缩庭园
题意: 给出一个 的网格,你可以随意填充黑色,但是必须保证任意两个黑色单元格不共享一条边,并且这个最终的图是对称的,问最多填充多少个黑色方块
网格图有个结论是最多选出 个不相邻的单元格
如果 中有一个是奇数,那么交替填充就可以到达这个上界,问题是都是偶数怎么办,这时候就必须舍弃掉一行或者一列,结果就是
void solve(){ int n, m; cin >> n >> m; if(n % 2 || m % 2) { cout << (n * m + 1) / 2 << endl; }else { cout << n * m / 2 - min(n, m) << endl; }}1010-静海拾光
题意: 每个测试用例的共一行,两个数 和 ,表示查询所有元素和不超过 的非空正整数序列按字典序升序排序后的第 个序列。保证一定存在
这种问题我们首先要知道的是 ,总和是 时的序列一共有多少个,官方题解用的是类似数学归纳法证明的,优点是证明过程简单,但是你得观察出规律,这里有一个更好的证明方式
采用递推思想假设 是已知的来求 ,f(x) 中以1开头的序列可以由 的所有序列最前面加1得到,不以1开头的我们把每一个序列的第一个元素数值减一,发现得到的就是所有的 中的序列,所以 可以发现虽然 可能很大,但数列个数是指数增长的,超过的部分就不用统计了
由等比数列求和可以得到总和小于等于 的数组数量是 。 中以1开头的序列个数是 这个递推和之前是同一个思想,所以 时就可以确定这一位就是1了,接着找序列的剩余部分元素之和不超过 ,字典序排名为 的序列。如果大于1就把所有的序列最高位都减一,这样相对大小是不变的,接着找序列剩余部分元素之和不超过 ,字典序排名为 的序列。这和原问题是类似的,一次递推即可,复杂度
void solve(){ int x, k, n = 1; cin >> x >> k; vector<int> ans(x + 5); auto dfs = [&](auto&& dfs, int x, int k) -> void { // cerr << x << ' ' << k << endl; ans[n]++; if(k == 1) { n++; return; } if(x > 60 || k <= p[x - 1]) { n++; dfs(dfs, x - 1, k - 1); }else { dfs(dfs, x - 1, k - p[x - 1]); } }; dfs(dfs, x, k); fore(i, 1, n) cout << ans[i] << " \n"[i == n - 1];}如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时





