atcoder一类常见的折半问题.
枚举子集和枚举超集可以均摊,改为枚举一半子集,一半超级.
具体地:
其中枚举子集我写的太劣了还T了
P7252 [JSOI2011] 棒棒糖
区间查询绝对众数.
首先直接随机化就可以过,每次随机一个数钦定他是绝对众数,正确率很高.
然后一种确定性做法是回滚莫队(不是)
log做法是:
弄一个可持久化线段树,权值开,,每个版本对应下标前缀和
然后扫描线直接查即可
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;// 权值主席树(persistent segment tree)实现
// 值域 [1..MAXV]
const int MAXN = 50000 + 5;
const int MAXV = 50000; // 按题意 c_i <= 5e4
// 估计节点上限: n * (log2(MAXV)+2) 安全取 20 * MAXN
const int MAXNODE = MAXN * 20;int lc[MAXNODE], rc[MAXNODE], sumv[MAXNODE];
int roots[MAXN]; // roots[i] 表示前缀 i 的版本(i 从 0 开始,roots[0] = 0)
int tot = 1; // 0 号节点为空节点,节点编号从 1 开始int newnode() {// 返回一个新节点编号(已清零)++tot;lc[tot] = rc[tot] = 0;sumv[tot] = 0;return tot;
}// 在 prev 版本的树上插入位置 pos,返回新版本根节点编号
int update(int prev, int l, int r, int pos) {int cur = newnode();lc[cur] = lc[prev];rc[cur] = rc[prev];sumv[cur] = sumv[prev] + 1;if (l == r) {return cur;}int mid = (l + r) >> 1;if (pos <= mid) {lc[cur] = update(lc[prev], l, mid, pos);} else {rc[cur] = update(rc[prev], mid + 1, r, pos);}return cur;
}// 在区间 [l,r] 中,比较两个版本 u (root_r) 和 v (root_{l-1}),长度 len,寻找多数元素的值或 0
int query_major(int u, int v, int l, int r, int len) {if (l == r) {int cnt = sumv[u] - sumv[v];if (cnt > len / 2) return l;else return 0;}int mid = (l + r) >> 1;int cntLeft = sumv[ lc[u] ] - sumv[ lc[v] ];if (cntLeft > len / 2) {return query_major(lc[u], lc[v], l, mid, len);}int total = sumv[u] - sumv[v];int cntRight = total - cntLeft;if (cntRight > len / 2) {return query_major(rc[u], rc[v], mid + 1, r, len);}return 0;
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int n, m;if (!(cin >> n >> m)) return 0;vector<int> a(n + 1);for (int i = 1; i <= n; ++i) cin >> a[i];// 初始化空节点 0 的数据(tot 从 1 开始,所以 0 作为空节点)lc[0] = rc[0] = 0;sumv[0] = 0;// 确保 tot >= 1 且节点 1 留作第一个 newnode() 的结果tot = 1; lc[1] = rc[1] = 0;sumv[1] = 0;roots[0] = 0;// 构建每个前缀的版本for (int i = 1; i <= n; ++i) {// 在 roots[i-1] 上插入 a[i]roots[i] = update(roots[i-1], 1, MAXV, a[i]);}while (m--) {int l, r;cin >> l >> r;int len = r - l + 1;int ans = query_major(roots[r], roots[l - 1], 1, MAXV, len);cout << ans << '\n';}return 0;
}
P3594 [POI 2015 R3] 狼坑 Trous de loup
无敌好题.
枚举区间,再枚举减去的东西 n^3
枚举改成双指针 n^2
发现中间删除那一段是
可以用单调队列维护.
双指针过程中,右指针移动时维护单调,左指针移动时维护合法
#include<bits/stdc++.h>
using namespace std;#define maxn 2000005
long long n, p, d, ans;
long long a[maxn], sum[maxn];
int l, h, t, q[maxn];signed main() {if (!(cin >> n >> p >> d)) return 0;for(int i = 1; i <= n; ++ i) {if (!(cin >> a[i])) return 0;sum[i] = sum[i - 1] + a[i];}ans = d, q[t] = d, l = 1;for(int i = d + 1; i <= n; ++ i) {while(h <= t && sum[i] - sum[i - d] > sum[q[t]] - sum[q[t] - d]) -- t;q[++ t] = i;while(h <= t && sum[i] - sum[l - 1] - sum[q[h]] + sum[q[h] - d] > p) {++ l;while(h <= t && q[h] - d + 1 < l) ++ h;}ans = max(ans, (long long)i - l + 1);}cout << ans << endl;return 0;
}
是单调的