Drift数据库开发实战:类型安全的SQLite解决方案

捌匈严仑1.1 二分 & 双指针

关联博文:Atserkcn-0/1分数规划

P1404 平均数

既然要让子串平均数最大,那就二分平均数,判断能否达到即可。复杂度

(

log

?

)

O(nlogV)。

关联题目:[2025国庆集训Day2C] course

点击查看代码

P4047 [JSOI2010] 部落划分

要求距离最远的部落距离最小,依然二分答案。但是判定时需要贪心地选择最近的两个部落合并,需要用到并查集维护集合。时间复杂度

(

2

log

?

×

(

)

)

O(n

2

logV×α(n))。

点击查看代码

P6004 Wormhole Sort S

奶牛为什么要钻虫洞?

要求最大化被奶牛用来排序的虫洞宽度的最小值,还是二分答案。

考虑二分最小虫洞宽度。先对每个虫洞的宽度排序,然后判定的时候把满足

w≥mid 的虫洞两端点用并查集合并到一起,最后看每个

p

i

?

i 是否在同一集合内就行了。时间复杂度

(

log

?

log

?

)

O(nlogn+nlogV)。

点击查看代码

P4064 [JXOI2017] 加法

可以二分题目要求的

min

?

{

}

min{a

i

?

},但是判定很麻烦。

我们贪心地想,从左往右扫一遍,遇到小于

mid 的

a

i

?

就直接用包含它的

,

l,r 执行操作直到它足够大,但是这个操作对区间内其他元素也有影响,要用树状数组维护。

但是区间的使用是有限制的,考虑取用区间时按照右端点排序,这样可以贡献到更多后面未知元素。同时要弹出右端点在

i 之前的区间,用优先队列可以实现这一整个过程。

参考的题解说预先要按照左端点排序,但是事实上与优先队列采用一致的排序方式也不影响。

const int N = 2e5 + 5;

const long long INF = 1e18;

int T, n, m, k, a;

long long A[N], c[N];

struct Segs {

int l, r;

bool operator < (const Segs &rhs) const { return r < rhs.r; }

} seg[N];

inline int lbt(register int x) { return x & -x; }

inline void upd(register int x, register int d) { while(x <= n) c[x] += d, x += lbt(x); return; }

inline int qry(register int x) { int res = 0; while(x) res += c[x], x -= lbt(x); return res; }

inline bool cmp(const Segs &x, const Segs & y) {

if(x.l == y.l) return x.r > y.r;

return x.l < y.l;

}

priority_queue q;

inline void init() {

while(!q.empty()) q.pop();

memset(c, 0, sizeof(c));

return;

}

inline bool chk(int x) {

init();

int rec = 1, cnt = 0;

for(int i = 1; i <= n; i++) upd(i, A[i] - A[i - 1]);

for(int i = 1; i <= n; i++) {

while(rec <= m && seg[rec].l <= i) {

q.push(seg[rec]);

rec++;

}

while(qry(i) < x && !q.empty()) {

Segs t;

do {

t = q.top();

q.pop();

} while(t.r < i && !q.empty());

cnt++;

if(cnt > k || t.r < i) return false;

upd(t.l, a); upd(t.r + 1, -a);

}

if(qry(i) < x) return false;

}

return true;

}

void Main() {

read(n); read(m); read(k); read(a);

long long l = INF, r = 0, mid, ans = 0;

for(register int i = 1; i <= n; i++) {

read(A[i]);

l = min(l, A[i]); r = max(r, A[i]);

}

for(register int i = 1; i <= m; i++) {

read(seg[i].l); read(seg[i].r);

}

sort(seg + 1, seg + m + 1, cmp);

r += k * a;

while(l <= r) {

mid = (l + r) >> 1;

if(chk(mid)) l = mid + 1, ans = mid;

else r = mid - 1;

}

fwr(ans);

return putchar('\n'), void();

}

signed main(){

read(T); while(T--) Main();

return 0;

}

CF1731D Valiant's New Map

本题有个技巧是二分判定时预先把矩阵变成 01 矩阵,再做二维前缀和找和为

0

0 的正方形就行了。

P2824 [HEOI2016/TJOI2016] 排序

放我做题计划里多久了?忘了

一眼线段树。但是这题实际上是离线的,线段树主要起到辅助二分判定的作用。

正常排序是

(

log

?

)

O(nlogn) 的,显然太慢。本题需要一个更快捷的排序,那就是 01 排序。

01 排序只需要记一下

1

1 的个数,然后区间统一修改就行了。应用到本题,即二分答案

mid,然后大于等于

mid 的全标记成

1

1,否则标记成

0

0,如果判定后

mid 为

1

1 左端点后移,否则右端点前移。线段树上查询和上传修改操作全部都是

(

log

?

)

O(logn) 的,判定复杂度就是

(

log

?

)

O(nlogn),总复杂度

(

log

?

2

)

O(nlog

2

n)。

#include

using namespace std;

const int N = 1e5 + 5;

int n, m, q;

int a[N], op[N], L[N], R[N];

struct SGT {

int l, r, val, lzy;

} tr[N << 2];

void pushup(int rt) {

tr[rt].val = tr[(rt << 1)].val + tr[(rt << 1 | 1)].val;

return;

}

void rebuild(int l, int r, int rt, int mid) {

tr[rt].l = l, tr[rt].r = r, tr[rt].lzy = 0;

if(l == r) {

if(a[l] >= mid) tr[rt].val = 1;

else tr[rt].val = 0;

return;

}

int md = (l + r) >> 1;

rebuild(l, md, (rt << 1), mid);

rebuild(md + 1, r, (rt << 1 | 1), mid);

pushup(rt);

return;

}

void pushdown(int rt) {

if(tr[rt].lzy) {

tr[rt << 1].lzy = tr[rt << 1 | 1].lzy = tr[rt].lzy;

int mid = (tr[rt].l + tr[rt].r) >> 1;

if(tr[rt].lzy == 1) tr[(rt << 1)].val = mid - tr[rt].l + 1, tr[(rt << 1 | 1)].val = tr[rt].r - mid;

else tr[rt << 1].val = tr[rt << 1 | 1].val = 0;

tr[rt].lzy = 0;

}

return;

}

int qry(int l, int r, int rt, int ql, int qr) {

if(ql <= l && r <= qr) return tr[rt].val;

if(ql > r || qr < l) return 0;

pushdown(rt);

int mid = (l + r) >> 1;

return qry(l, mid, rt << 1, ql, qr) + qry(mid + 1, r, rt << 1 | 1, ql, qr);

}

int check(int l, int r, int rt, int p) {

if(l == p && r == p) return tr[rt].val;

pushdown(rt);

int mid = (l + r) >> 1;

if(p <= mid) return check(l, mid, (rt << 1), p);

else return check(mid + 1, r, (rt << 1 | 1), p);

}

void upd(int l, int r, int rt, int ql, int qr, int v) {

if(ql <= l && qr >= r) {

tr[rt].val = v * (r - l + 1);

tr[rt].lzy = v ? 1 : -1;

return;

}

if(ql > r || qr < l) return;

pushdown(rt);

int mid = (l + r) >> 1;

upd(l, mid, (rt << 1), ql, qr, v);

upd(mid + 1, r, rt << 1 | 1, ql, qr, v);

pushup(rt);

}

bool chk(int mid) {

rebuild(1, n, 1, mid);

for(int i = 1; i <= m; i++) {

int cnt1 = qry(1, n, 1, L[i], R[i]);

if(op[i] == 0) {

upd(1, n, 1, R[i] - cnt1 + 1, R[i], 1);

upd(1, n, 1, L[i], R[i] - cnt1, 0);

}

else {

upd(1, n, 1, L[i], L[i] + cnt1 - 1, 1);

upd(1, n, 1, L[i] + cnt1, R[i], 0);

}

}

return check(1, n, 1, q);

}

int main(){

cin >> n >> m;

for(int i = 1; i <= n; i++) {

cin >> a[i];

}

for(int i = 1; i <= m; i++) {

cin >> op[i] >> L[i] >> R[i];

}

cin >> q;

int l = 1, r = n, mid, ans;

while(l <= r) {

mid = (l + r) >> 1;

if(chk(mid)) l = mid + 1, ans = mid;

else r = mid - 1;

}

cout << ans;

return 0;

}

ABC136E Max GCD

本文可以在题解区找到。

多好的一道思维题!

蒟蒻觉得大佬们双指针贪心部分讲得有些简略,故写一篇题解来具体说说自己的想法。

首先,我们要发现一个重要的性质:记最终的答案为

ans,那么

ans 一定整除

=

1

i=1

n

?

A

i

?

。因为无论怎么操作序列的和都不变,且最后必须保证

ans 是所有

A

i

?

的公约数。

那么,我们记

=

=

1

sum=∑

i=1

n

?

A

i

?

,则

ans 一定是

sum 的因子。那就需要枚举

sum 的因子,合法的最大因子就是

ans。

接下来就是判断是否合法。当考察因子

x 时,考虑记录每个

=

m

o

d

B

i

?

=A

i

?

modx,并将它们从小到大排序,接着进行双指针扫描,贪心地计算所需操作次数

cnt,若

cnt≤k 则合法。记左指针为

l,右指针为

r,具体贪心实现如下:

=

B

l

?

+B

r

?

=x,那么操作

B

l

?

,

B

l

?

,B

r

?

就可以都变成

0

0,

cnt 加上

B

l

?

,两个指针都向中间靠

1

1 个坐标。

<

B

l

?

+B

r

?

B

l

?

次使得

B

l

?

变成

0

0,则

B

r

?

变成

B

r

?

+B

l

?

cnt 加上

B

l

?

,左指针向中间靠

1

1 个坐标。

>

B

l

?

+B

r

?

>x,那么钦定操作

?

x?B

r

?

次使得

B

r

?

变成

x,相当于变成

0

0,则

B

l

?

变成

?

B

l

?

?x+B

r

?

cnt 加上

?

x?B

r

?

,右指针向中间靠

1

1 个坐标。

如果你对后两个操作有疑问,如"为什么要这样钦定",请代入前提条件"已经将

B

i

?

从小到大排序"。

那么本题就完成了,时间复杂度是

(

log

?

)

O(nlogn

sum

?

)。但实际上肯定要比这个快,因为

(

log

?

)

O(nlogn) 是检查因数时排序的复杂度。

提交记录。

1.2 贪心

P8898 Feeding the Cows B

注意到有的地方如果种了草,那么它所能影响的范围内均不需要种草。那就考虑在从左往右扫的时候,如果

i 没被覆盖,就不断从

i+k 往前看能不能种,如果能种就直接跳出。

时间复杂度大概

(

log

?

)

O(nlogn)。

P3545 [POI 2012] HUR-Warehouse Store

经典的贪心+堆维护。

最优肯定是全部都满足,但是大概率不可能。考虑能满足就满足,遇到不能满足的就把前面满足的人里买的最多的那个人踢出去(如果踢掉他就能满足当前的人),这样就能保证答案最大化。

用大根堆维护即可,复杂度

(

log

?

)

O(nlogn)。

struct node {

int id, mon;

bool operator < (const node &rhs) const {

return mon < rhs.mon;

}

};

priority_queue q;

signed main() {

rd(n);

for(int i = 1; i <= n; i++) {

rd(a[i]);

}

for(int i = 1; i <= n; i++) {

rd(b[i]);

}

for(int i = 1; i <= n; i++) {

sum += a[i];

if(sum < b[i]) {

if(!q.empty() && q.top().mon > b[i]) {

vis[q.top().id] = 0;

sum += q.top().mon;

q.pop();

--ans;

}

}

if(sum >= b[i]) {

sum -= b[i];

q.push((node){i, b[i]});

vis[i] = 1;

++ans;

}

}

fp(ans); hh();

for(int i = 1; i <= n; i++, kg()) {

if(vis[i]) fp(i);

}

return 0;

}

P4053 [JSOI2007] 建筑抢修

贪心+堆模型。

限制时间短的建筑肯定更要紧,所以先按照它升序排序。

接下来就是自然的贪心。如果有建筑需要报废,那必然选择报废维修时间最长那个来争取维修更多建筑。

struct Build {

int t1, t2;

} a[N];

priority_queueq;

bool cmp(Build x, Build y) {

return x.t2 < y.t2;

}

signed main() {

rd(n);

for(int i = 1; i <= n; i++) {

rd(a[i].t1); rd(a[i].t2);

}

sort(a + 1, a + n + 1, cmp);

for(int i = 1; i <= n; i++) {

sum += a[i].t1;

q.push({a[i].t1, i});

if(sum <= a[i].t2) ans++;

else sum -= q.top().first, q.pop();

}

fp(ans);

return 0;

}

1.3 搜索 & 模拟

P12684 叉叉学习魔法

考虑直接 bfs,容易想到记录当前位置、最小步数

1

t1 和最少魔法使用次数

2

t2,用堆维护可以做到

(

log

?

(

)

)

O(nmlog(nm))。但是这样会超时倒闭,所以考虑用 01bfs,将

1

1

t1+1 的新状态加到队尾,

2

1

t2+1 的新状态加到队头,这样相当于线性维护了之前的堆,可以把

log

?

log 去掉做到

(

)

O(nm)。当然,在此之前

1

,

2

t1,t2 没改的状态要从队头取出,否则会破坏单调性。

#include

#include

#include

using namespace std;

const int N = 5005;

int n, m, x1, y1, x2, y2;

char a[N][N];

bool vis[N][N];

struct node {

int x, y, t1, t2;

bool operator < (const node &rhs) const {

if(t1 != rhs.t1) return t1 > rhs.t1;

return t2 > rhs.t2;

}

};

deque q;

vector v1, v2;

const int dx1[] = {-1, 1, 0, 0}, dy1[] = {0, 0, -1, 1};

const int dx2[] = {-1, -1, 1, 1}, dy2[] = {-1, 1, -1, 1};

void _01bfs() {

q.push_front((node){x1, y1, 0, 0});

while(!q.empty()) {

node u = q.front();

while(!q.empty() && u.t1 == q.front().t1 && u.t2 == q.front().t2) {

u = q.front();

q.pop_front();

if(u.x == x2 && u.y == y2) {

printf("%d %d", u.t1, u.t2);

return;

}

if(vis[u.x][u.y]) continue;

vis[u.x][u.y] = 1;

for(int i = 0; i < 4; i++) {

int nx = u.x + dx1[i], ny = u.y + dy1[i];

if(!nx || !ny || nx == n + 1 || ny == m + 1 || a[nx][ny] == '#') continue;

v1.push_back({nx, ny, u.t1 + 1, u.t2});

}

for(int i = 0; i < 4; i++) {

int nx = u.x + dx2[i], ny = u.y + dy2[i];

if(!nx || !ny || nx == n + 1 || ny == m + 1 || a[nx][ny] == '#') continue;

v2.push_back({nx, ny, u.t1, u.t2 + 1});

}

}

for(auto v : v2) q.push_front(v);

for(auto v : v1) q.push_back(v);

v1.clear(), v2.clear();

}

printf("-1 -1");

return;

}

int main() {

scanf("%d %d", &n, &m);

for(int i = 1; i <= n; i++) {

scanf("%s", a[i] + 1);

for(int j = 1; j <= m; j++) {

if(a[i][j] == 'X') x1 = i, y1 = j;

else if(a[i][j] == 'W') x2 = i, y2 = j;

}

}

_01bfs();

return 0;

}

P8817 [CSP-S 2022] 假期计划

布什戈门,说好的折半呢???这哪里有啊?

先暴力处理两两之间的连通性,复杂度

(

2

)

O(n

2

)。处理的时候每个点存可以抵达的点中权值最大的三个点。最后算答案的时候直接四重循环更新答案,但是由于我们之前存的是三个可以抵达的最大权值点,所以显然复杂度只有

(

2

)

O(n

2

)。

#include

#define int long long

using namespace std;

const int N = 2505, M = 20005;

inline void rd(int &x) {scanf("%lld", &x);}

inline void fp(int &x) {printf("%lld", x);}

inline void hh() {putchar('\n');}

inline void kg() {putchar(' '); }

int n, m, k, val[N];

int head[N], totot;

int dis[N];

bool to[N][N];

vector g[N];

struct EDGE {int nxt, to; } edge[M];

inline void addedge(int u, int v) {

edge[++totot].to = v, edge[totot].nxt = head[u], head[u] = totot;

return;

}

inline bool cmp(int x, int y) {

return val[x] > val[y];

}

inline void bfs(int x) {

for(int i = 1; i <= n; i++) dis[i] = -1;

dis[x] = 0;

queue q;

q.push(x);

while(!q.empty()) {

int u = q.front();

q.pop();

if(u != x) {

to[x][u] = 1;

if(x != 1 && to[1][u]) {

g[x].push_back(u);

sort(g[x].begin(), g[x].end(), cmp);

if(g[x].size() > 3) g[x].pop_back();

}

}

if(dis[u] == k + 1) continue;

for(int i = head[u]; i; i = edge[i].nxt) {

int v = edge[i].to;

if(dis[v] == -1) {

dis[v] = dis[u] + 1;

q.push(v);

}

}

}

}

signed main() {

rd(n), rd(m), rd(k);

for(int i = 2; i <= n; i++) {

rd(val[i]);

}

for(int i = 1; i <= m; i++) {

int x, y; rd(x), rd(y);

addedge(x, y), addedge(y, x);

}

for(int i = 1; i <= n; i++) bfs(i);

int ans = 0;

for(int i = 2; i <= n; i++) {

for(int j = 2; j <= n; j++) {

if(to[i][j]) {

for(int k : g[i]) {

for(int l : g[j]) {

if(j != k && i != l && k != l) {

ans = max(ans, val[i] + val[j] + val[k] + val[l]);

}

}

}

}

}

}

fp(ans);

return 0;