[杂项] 刷题记录

点分治

  1. Luogu P3806 【模板】点分治 1

静态点分治模板题;

  1. Luogu P6329 【模板】点分树 | 震波

动态点分治模板题;

  1. Luogu P4206 [NOI2005] 聪聪与可可

板子题,记一下 mod 3 意义下余数分别为 1 2 0 的个数,合并时统计即可;

  1. Luogu P4149 [IOI2011] Race

板子题,开个二元组记录一下权值和边数即可;

  1. Luogu P4178 Tree

板子题,和第一题类似,只不过开个树状数组记录一下前缀和,然后就解决了;

  1. Luogu P3714 [BJOI2017] 树的难题

这题。。。我TM调了三个小时,结果学校OJ上还得卡常!!!

只要用上线段树等数据结构,学校OJ就过不去

顺便发泄一下自己的情绪:上次模拟赛T2开了27个线段树,常数确实有些大,但是Luogu上过了,学校OJ上就咋都过不去;
你可能会说,学校OJ咋能和Luogu比呢?
但今天上午,一道虚树的入门题,时限2s,在Luogu上跑刚过1s,结果在学校OJ上直接TLE俩点,经过我严谨的时间复杂度分析,大约是2e8+1e7,我就不理解了,就TM多这么100ms咋就跑不过去了?(其实可能确实是我菜,整不出题解的优秀复杂度),跟题解一比,多了个线段树的复杂度,整的我现在打比赛都不敢用线段树,但就是每场比赛都TM能想出来用线段树的卡常做法,结果今天上午这题经过_lhx_和cpa一个多小时的大力卡常才勉强过;
现在能力没咋提升,倒是卡常进步不少;

回归正题;

其实思路不难,但细节太多了。。。

首先,路径还是能拆分成两类:经过根的和不经过根的;

所以可以点分治;

首先将每个点的儿子按大小排序,因为这样我们就可以比较方便的处理到根的路径颜色相同的子树们;

然后进行点分治,我们开两个线段树,把与当前路径颜色相同的放进一个线段树,不同的放进一个线段树,然后正常跑就行;

注意线段树的清空,可以直接在根节点上打懒标记;

然后就是一些细节,不说了,可以看代码;

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
int n, m, l, r;
int c[500005];
int rt, sum;
struct sss{
	int t, ne, w;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v, int ww) {
	e[++cnt].t = v;
	e[cnt].ne = h[u];
	e[cnt].w = ww;
	h[u] = cnt;
}
vector<pair<int, int> > v[200005];
struct sas{
	int dis, sum;
}dis[200005], rem[200005], po[200005];
int maxp[1000005], siz[1000005], dep[1000005];
bool vis[1000005];
int ans;
namespace seg{
	inline int ls(int x) {
		return x << 1;
	}
	inline int rs(int x) {
		return x << 1 | 1;
	}
	struct asa{
		int l, r, ma, lz;
	}tr[2][900005];
	inline void push_up(int s, int id) {
		tr[s][id].ma = max(tr[s][ls(id)].ma, tr[s][rs(id)].ma);
	}
	inline void push_down(int s, int id) {
		if(tr[s][id].lz != 0) {
			tr[s][ls(id)].lz = tr[s][id].lz;
			tr[s][rs(id)].lz = tr[s][id].lz;
			tr[s][ls(id)].ma = tr[s][id].lz;
			tr[s][rs(id)].ma = tr[s][id].lz;
			tr[s][id].lz = 0;
		}
	}
	void bt(int s, int id, int l, int r) {
		tr[s][id].l = l;
		tr[s][id].r = r;
		if (l == r) {
			tr[s][id].ma = -0x3f3f3f3f;
			tr[s][id].lz = 0;
			return;
		}
		int mid = (l + r) >> 1;
		bt(s, ls(id), l, mid);
		bt(s, rs(id), mid + 1, r);
		push_up(s, id);
	}
	inline void clear(int s) {
		tr[s][1].lz = -0x3f3f3f3f;
		tr[s][1].ma = -0x3f3f3f3f;
	}
	int ask(int s, int id, int l, int r) {
		if (tr[s][id].l >= l && tr[s][id].r <= r) {
			return tr[s][id].ma;
		}
		push_down(s, id);
		int mid = (tr[s][id].l + tr[s][id].r) >> 1;
		if (r <= mid) return ask(s, ls(id), l, r);
		else if (l > mid) return ask(s, rs(id), l, r);
		else return max(ask(s, ls(id), l, mid), ask(s, rs(id), mid + 1, r));
	}
	void add(int s, int id, int pos, int d) {
		if (tr[s][id].l == tr[s][id].r) {
			tr[s][id].ma = max(tr[s][id].ma, d);
			tr[s][id].lz = 0;
			return;
		}
		push_down(s, id);
		int mid = (tr[s][id].l + tr[s][id].r) >> 1;
		if (pos <= mid) add(s, ls(id), pos, d);
		else add(s, rs(id), pos, d);
		push_up(s, id);
	}
}
void get_rt(int x, int f) {
	siz[x] = 1;
	maxp[x] = 0;
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (u == f || vis[u]) continue;
		get_rt(u, x);
		siz[x] += siz[u];
		maxp[x] = max(maxp[x], siz[u]);
	}
	maxp[x] = max(maxp[x], sum - siz[x]);
	if (maxp[rt] > maxp[x]) rt = x;
}
void get_dis(int x, int f, int pre) {
	dep[x] = dep[f] + 1;
	if (dep[x] > r) return;
	dis[x].dis = dep[x];
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (vis[u] || u == f) continue;
		dis[u].sum = dis[x].sum; //注意继承的问题;
		if (e[i].w != pre && e[i].w) dis[u].sum += c[e[i].w]; //注意判断;
		get_dis(u, x, e[i].w);
	}
}
void dfs(int x, int f) {
	if (dis[x].dis == 0) return;
	rem[++rem[0].sum] = sas{dis[x].dis, dis[x].sum};
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (u == f || vis[u]) continue;
		dfs(u, x);
	}
}
void calc(int x) {
	int color = 0;
	int o = 0;
	dep[x] = 0;
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (vis[u]) continue;
		if (color == 0) {
			color = e[i].w;
		} else if (color != e[i].w) {
			color = e[i].w;
			seg::clear(1);
			for (int j = 1; j <= o; j++) {
				seg::add(0, 1, po[j].dis, po[j].sum);
			}
			o = 0;
		}
		rem[0].sum = 0;
		dis[u].sum = c[e[i].w];
		get_dis(u, x, e[i].w);
		dfs(u, x);
		for (int j = 1; j <= rem[0].sum; j++) {
			if (rem[j].dis > r) continue;
			if (rem[j].dis >= l && rem[j].dis <= r) {
				ans = max(ans, rem[j].sum);
			}
			if (rem[j].dis == r) continue;
			if (rem[j].dis == 0) continue;
			int aa = seg::ask(0, 1, max(0, l - rem[j].dis), r - rem[j].dis);
			int bb = seg::ask(1, 1, max(0, l - rem[j].dis), r - rem[j].dis);
			bb -= c[e[i].w];
			ans = max(ans, max(rem[j].sum + aa, rem[j].sum + bb));
		}
		for (int j = 1; j <= rem[0].sum; j++) {
			if (rem[j].dis == 0) continue;
			o++;
			po[o].dis = rem[j].dis;
			po[o].sum = rem[j].sum;
		}
		for (int j = 1; j <= rem[0].sum; j++) {
			if (rem[j].dis == 0) continue;
			seg::add(1, 1, rem[j].dis, rem[j].sum);
		}
	}
	seg::clear(0);
	seg::clear(1);
}
void solve(int x) {
	vis[x] = true;
	calc(x);
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (vis[u]) continue;
		rt = 0;
		maxp[rt] = 0x3f3f3f3f;
		sum = siz[u];
		get_rt(u, 0);
		solve(rt);
	}
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m >> l >> r;
	for (int i = 1; i <= m; i++) {
		cin >> c[i];
	}
	int x, y, w;
	for (int i = 1; i <= n - 1; i++) {
		cin >> x >> y >> w;
		v[x].push_back({w, y});
		v[y].push_back({w, x});
	}
	for (int i = 1; i <= n; i++) {
		sort(v[i].begin(), v[i].end());
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < v[i].size(); j++) {
			add(i, v[i][j].second, v[i][j].first);
		}
	}
	seg::bt(0, 1, 0, n);
	seg::bt(1, 1, 0, n);
	ans = -0x3f3f3f3f;
	rt = 0;
	maxp[rt] = 0x3f3f3f3f;
	sum = n;
	get_rt(1, 0);
	solve(rt);
	cout << ans;
	return 0;
}

貌似题解有单调队列的优秀做法,但我不会,有兴趣的可以去看看;

可能是我目光短浅,感觉点分治这玩意就是模板 + 数据结构维护,没啥的;

当然前提是得看出来是点分治。。。

走了,去卡常了

CDQ分治

详见:一些分治 中的CDQ分治;

  1. Luogu P3810 【模板】三维偏序(陌上花开)

板子题,排序后直接干就行;

  1. Luogu P4390 [BalkanOI2007] Mokia 摩基亚

把时间 t 看作第一个限制条件,x,y 看作第二个和第三个,每次只有都小于等于的修改操作才能产生贡献;

同时利用一下二维差分,把一次查询看成四个小查询,但要注意可能会减成 0,这样会导致树状数组死循环,所以所有输入的横纵坐标,以及 n 值都 +1 即可;

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int s, w, x, y, xx, yy;
inline int lowbit(int x) {
	return x & (-x);
}
struct sss{
	int t, x, y, w;
	bool s;
}e[500005];
int n;
bool cmpx(sss x, sss y) {
	if (x.x == y.x) return x.y < y.y;
	else return x.x < y.x;
}
bool cmp(sss x, sss y) {
	return x.t < y.t;
}
int tr[3000005];
void add(int pos, int d) {
	for (int i = pos; i <= w; i += lowbit(i)) tr[i] += d;
}
int ask(int pos) {
	int ans = 0;
	for (int i = pos; i; i -= lowbit(i)) ans += tr[i];
	return ans;
}
void cdq(int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	cdq(l, mid);
	cdq(mid + 1, r);
	sort(e + l, e + mid + 1, cmpx);
	sort(e + mid + 1, e + r + 1, cmpx);
	int i = mid + 1;
	int j = l;
	while(i <= r) {
		while(e[i].x >= e[j].x && j <= mid) {
			if (e[j].s == 0) add(e[j].y, e[j].w);
			j++;
		}
		if (e[i].s == 1) {
			e[i].w += ask(e[i].y);
		}
		i++;
	}
	for (int i = l; i < j; i++) {
		if (e[i].s == 0) add(e[i].y, -e[i].w);
	}
}
int main() {
	while(cin >> s) {
		if (s == 3) break;
		if (s == 0) {
			cin >> w;
			w++;
		}
		if (s == 1) {
			cin >> x >> y >> xx;
			x++;
			y++;
			e[++n] = {n, x, y, xx, 0};
		}
		if (s == 2) {
			cin >> x >> y >> xx >> yy;
			x++;
			y++;
			xx++;
			yy++;
			e[++n] = {n, xx, yy, 0, 1};
			e[++n] = {n, x - 1, y - 1, 0, 1};
			e[++n] = {n, xx, y - 1, 0, 1};
			e[++n] = {n, x - 1, yy, 0, 1};
		}
	}
	cdq(1, n);
	sort(e + 1, e + 1 + n, cmp);
	int i = 1;
	while(i <= n) {
		if (e[i].s == 1) {
			cout << e[i].w + e[i + 1].w - e[i + 2].w - e[i + 3].w << endl;
			i += 4;
		} else {
			i++;
		}
	}
	return 0;
}
  1. Luogu P4169 [Violet] 天使玩偶/SJY摆棋子

对于每一个询问 (x,y),我们要求:

min(|xxx|+|yyy|)

其中,(xx,yy) 代表已经有的一个点;

依据以往的套路,我们想着要去掉绝对值,于是对于每一个 (x,y),我们将现在有的 (xx,yy) 分成四种,为在 (x,y) 的左下方,左上方,右上方,右下方,分别用 o=1,o=2,o=3,o=4 表示;

那么我们要求的就变成了:

min{o=1  x+y(xx+yy)(xxx,yyy)o=2  xy(xxyy)(xxx,yyy)o=3  xy(xxyy)(xxx,yyy)o=4  yx(yyxx)(xxx,yyy)

当然,只有时间在 询问 (x,y) 之前的修改才有贡献;

那么这就是四个三维偏序问题,用四次CDQ求解即可;

当然,你也可以进行一些变换,使其变成三个相同条件(但也是要用四次CDQ);

注意每次CDQ前都要按时间重新排一次序

时间复杂度:Θ((n+m)log(n+m)logma),其中 ma 是输入横纵坐标的最大值;

但常数很大,注意卡常;

加了一点树状数组的剪枝

注意树状数组中 0 的情况!

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define FI(n) FastIO::read(n)
#define FO(n) FastIO::write(n)
#define Flush FastIO::Fflush()
namespace FastIO {
    const int SIZE=1<<16;
    char buf[SIZE],obuf[SIZE],str[60];
    int bi=SIZE,bn=SIZE,opt;
    inline int read(register char *s) {
        while(bn){
            for(;bi<bn&&buf[bi]<=' ';bi=-~bi);
            if(bi<bn)break;
            bn=fread(buf,1,SIZE,stdin);
            bi&=0;
        }
        register int sn=0;
        while(bn){
            for(;bi<bn&&buf[bi]>' ';bi=-~bi)s[sn++]=buf[bi];
            if(bi<bn)break;
            bn=fread(buf,1,SIZE,stdin);
            bi&=0;
        }
        s[sn]&=0;
        return sn;
    }
    inline bool read(register int &x){
        int n=read(str),bf=0;
        if(!n)return 0;
        register int i=0;
        (str[i]=='-')&&(bf=1,i=-~i);
		(str[i]=='+')&&(i=-~i);
        for(x=0;i<n;i=-~i)x=(x<<3)+(x<<1)+(str[i]^48);
        bf&&(x=~x+1);
        return 1;
    }
    inline bool read(register long long &x) {
        int n=read(str),bf=1;
        if(!n)return 0;
        register int i=0;
        (str[i]=='-')&&(bf=-1,i=-~i);
        for(x=0;i<n;i=-~i)x=(x<<3)+(x<<1)+(str[i]^48);
        (bf<0)&&(x=~x+1);
        return 1;
    }
    inline void write(register int x) {
        if(!x)obuf[opt++]='0';
        else{
            (x<0)&&(obuf[opt++]='-',x=~x+1);
            register int sn=0;
            while(x)str[sn++]=x%10+'0',x/=10;
            for(register int i=sn-1;i>=0;i=~-i)obuf[opt++]=str[i];
        }
        (opt>=(SIZE>>1))&&(fwrite(obuf,1,opt,stdout),opt&=0);
    }
    inline void write(register long long x) {
        if(!x)obuf[opt++]='0';
        else{
            (x<0)&&(obuf[opt++]='-',x=~x+1);
            register int sn=0;
            while(x)str[sn++]=x%10+'0',x/=10;
            for(register int i=sn-1;i>=0;i=~-i)obuf[opt++]=str[i];
        }
        (opt>=(SIZE>>1))&&(fwrite(obuf,1,opt,stdout),opt&=0);
    }
    inline void write(register unsigned long long x){
        if(!x)obuf[opt++]='0';
        else{
            register int sn=0;
            while(x)str[sn++]=x%10+'0',x/=10;
            for(register int i=sn-1;i>=0;i=~-i)obuf[opt++]=str[i];
        }
        (opt>=(SIZE>>1))&&(fwrite(obuf,1,opt,stdout),opt&=0);
    }
    inline void write(register char x) {
        obuf[opt++]=x;
        (opt>=(SIZE>>1))&&(fwrite(obuf,1,opt,stdout),opt&=0);
    }
    inline void Fflush(){
        opt&&fwrite(obuf,1,opt,stdout);
        opt&=0;
    }
};
int n, m;
int ma;
namespace BIT{
	int tr[2000005];
	inline int lowbit(int x) {
		return x & (-x);
	}
	inline void clear() {
		for (int i = 1; i <= ma + 1; i++) tr[i] = -0x3f3f3f3f;
	}
	void add(int s, int pos, int d) {
		pos++;
		if (s == 1) for (register int i = pos; i <= ma + 1; i += lowbit(i)) {
			if (tr[i] >= d && d != -0x3f3f3f3f) return; 
			else tr[i] = (d == -0x3f3f3f3f ? d : max(tr[i], d));
		} else {
			for (register int i = pos; i; i -= lowbit(i)){
				if (tr[i] >= d && d != -0x3f3f3f3f) return;
				else tr[i] = (d == -0x3f3f3f3f ? d : max(tr[i], d));
			}
		}
	}
	int ask(int s, int d) {
		d++;
		int ans = -0x3f3f3f3f;
		if (s == 1) for (register int i = d; i; i -= lowbit(i)) ans = max(ans, tr[i]);
		else for (register int i = d; i <= ma + 1; i += lowbit(i)) ans = max(ans, tr[i]);
		return ans;
	}
}
using namespace BIT;
int tim, cnt;
struct sss{
	int t, x, y, s, ans;
}e[5000005], t[5000005];
bool cmpx(sss x, sss y) {
	if (x.x == y.x) return x.y < y.y;
	else return x.x < y.x;
}
bool cmpxx(sss x, sss y) {
	if (x.x == y.x) return x.y > y.y;
	else return x.x > y.x;
}
bool cmp(sss x, sss y) {
	return x.t < y.t;
}
void cdq(int o, int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	cdq(o, l, mid);
	cdq(o, mid + 1, r);
	int i = mid + 1;
	int j = l;
	while(i <= r) {
		if (o <= 2) {
			while(j <= mid && e[j].x <= e[i].x) {
				if (e[j].s == 1) {
					if (o == 1) {
						add(1, e[j].y, e[j].x + e[j].y);
					} else if (o == 2) {
						add(2, e[j].y, e[j].x - e[j].y);
					}
				}
				j++;
			}
		} else {
			while(j <= mid && e[j].x >= e[i].x) {
				if (e[j].s == 1) {
					if (o == 3) {
						add(2, e[j].y, -(e[j].x + e[j].y));
					} else if (o == 4) {
						add(1, e[j].y, e[j].y - e[j].x);
					}
				}
				j++;
			}
		}
		if (e[i].s == 2) {
			if (o == 1) {
				e[i].ans = min(e[i].ans, e[i].x + e[i].y - ask(1, e[i].y));
			} else if (o == 2) {
				e[i].ans = min(e[i].ans, e[i].x - e[i].y - ask(2, e[i].y));
			} else if (o == 3) {
				e[i].ans = min(e[i].ans, -e[i].x - e[i].y - ask(2, e[i].y));
			} else if (o == 4) {
				e[i].ans = min(e[i].ans, e[i].y - e[i].x - ask(1, e[i].y));
			}
		}
		i++;
	}
	for (register int i = l; i < j; i++) {
		if (o == 1 || o == 4) add(1, e[i].y, -0x3f3f3f3f);
		else add(2, e[i].y, -0x3f3f3f3f);
	}
	i = mid + 1;
	j = l;
	int k = l - 1;
	if (o <= 2) {
		while(i <= r) {
			while(j <= mid && cmpx(e[j], e[i])) {
				t[++k] = e[j];
				j++;
			}
			t[++k] = e[i];
			i++;
		}
	} else {
		while(i <= r) {
			while(j <= mid && cmpxx(e[j], e[i])) {
				t[++k] = e[j];
				j++;
			}
			t[++k] = e[i];
			i++;
		}
	}
	while(i <= r) {
		t[++k] = e[i];
		i++;
	}
	while(j <= mid) {
		t[++k] = e[j];
		j++;
	}
	for (register int i = l; i <= r; i++) e[i] = t[i];
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	FI(n);
	FI(m);
	int x, y;
	for (register int i = 1; i <= n; i++) {
		tim++;
		FI(x);
		FI(y);
		e[++cnt] = {tim, x, y, 1, 0x3f3f3f3f};
		ma = max(ma, y);
	}
	int ss;
	for (register int i = 1; i <= m; i++) {
		FI(ss);
		FI(x);
		FI(y);
		ma = max(ma, y);
		tim++;
		e[++cnt] = {tim, x, y, ss, 0x3f3f3f3f};
	}
	clear();
	cdq(1, 1, cnt);
	sort(e + 1, e + 1 + cnt, cmp);
	clear();
	cdq(2, 1, cnt);
	sort(e + 1, e + 1 + cnt, cmp);
	clear();
	cdq(3, 1, cnt);
	sort(e + 1, e + 1 + cnt, cmp);
	clear();
	cdq(4, 1, cnt);
	sort(e + 1, e + 1 + cnt, cmp);
	for (register int i = 1; i <= cnt; i++) {
		if (e[i].s == 2) {
			FO(e[i].ans);
			FO('\n');
		}
	}
	Flush;
	return 0;
}
  1. Luogu P3157 [CQOI2011] 动态逆序对

之前在CSDN上看见一篇博客说划分树能解决动态逆序对,但他既没给讲解,也没给实现,我也没找到用划分树解决的题解,貌似不行吧(毕竟划分树是静态的。。。);

考虑能产生贡献的一对点对 (i,j) 需要满足的条件为:

ti<tj,posi<posj,vali>valj

或:

ti<tj,posi>posj,vali<valj

此时 ij 产生贡献;

我们将初始序列看作全 0 的,初始值看作插入,删除操作就是删除,那么前者作正贡献,后者作负贡献;

跑两遍三维偏序即可,时间复杂度:Θ(nlog2n)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m;
struct sss{
	long long t, pos, val, s, id;
}e[500005], t[500005];
long long pos[500005];
long long ans[500005];
long long cnt, tim;
namespace BIT{
	long long tr[500005];
	inline long long lowbit(long long x) {
		return x & (-x);
	}
	void add(long long p, long long d) {
		for (long long i = p; i <= n; i += lowbit(i)) tr[i] += d;
	}
	long long ask(long long p) {
		long long ans = 0;
		for (long long i = p; i; i -= lowbit(i)) ans += tr[i];
		return ans;
	}
}
using namespace BIT;
void cdq1(long long l, long long r) {
	if (l == r) return;
	long long mid = (l + r) >> 1;
	cdq1(l, mid);
	cdq1(mid + 1, r);
	long long i = mid + 1;
	long long j = l;
	while(i <= r) {
		while(j <= mid && e[j].pos <= e[i].pos) {
			add(e[j].val, e[j].s);
			j++;
		}
		ans[e[i].id] += e[i].s * (ask(n) - ask(e[i].val));
		i++;
	}
	for (long long i = l; i < j; i++) {
		add(e[i].val, -e[i].s);
	}
	i = mid + 1;
	j = l;
	long long k = l - 1;
	while(i <= r) {
		while(j <= mid && e[j].pos <= e[i].pos) {
			t[++k] = e[j];
			j++;
		}
		t[++k] = e[i];
		i++;
	}
	while(i <= r) {
		t[++k] = e[i];
		i++;
	}
	while(j <= mid) {
		t[++k] = e[j];
		j++;
	}
	for (long long i = l; i <= r; i++) {
		e[i] = t[i];
	}
}
void cdq2(long long l, long long r) {
	if (l == r) return;
	long long mid = (l + r) >> 1;
	cdq2(l, mid);
	cdq2(mid + 1, r);
	long long i = mid + 1;
	long long j = l;
	while(i <= r) {
		while(j <= mid && e[j].pos >= e[i].pos) {
			add(e[j].val, e[j].s);
			j++;
		}
		ans[e[i].id] += e[i].s * ask(e[i].val - 1);
		i++;
	}
	for (long long i = l; i < j; i++) {
		add(e[i].val, -e[i].s);
	}
	i = mid + 1;
	j = l;
	long long k = l - 1;
	while(i <= r) {
		while(j <= mid && e[j].pos >= e[i].pos) {
			t[++k] = e[j];
			j++;
		}
		t[++k] = e[i];
		i++;
	}
	while(i <= r) {
		t[++k] = e[i];
		i++;
	}
	while(j <= mid) {
		t[++k] = e[j];
		j++;
	}
	for (long long i = l; i <= r; i++) {
		e[i] = t[i];
	}
}
inline bool cmp(sss x, sss y) {
	return x.t < y.t;
}
int main() {
	cin >> n >> m;
	long long x;
	for (long long i = 1; i <= n; i++) {
		cin >> x;
		pos[x] = i;
		e[++cnt] = {++tim, i, x, 1, 0};
	}
	for (long long i = 1; i <= m; i++) {
		cin >> x;
		e[++cnt] = {++tim, pos[x], x, -1, i};
	}
	cdq1(1, cnt);
	sort(e + 1, e + 1 + cnt, cmp);
	cdq2(1, cnt);
	long long an = 0;
	for (long long i = 0; i < m; i++) {
		an += ans[i];
		cout << an << endl;
	}
	return 0;
}
  1. Luogu P4093 [HEOI2016/TJOI2016] 序列

注意:变换序列可以是原序列

这道题有一个新的套路:用CDQ搞DP;

我们设 mai 表示 i 这个位置能有的最大值, mii 表示能有的最小值, ai 表示原值,fi 表示以 i 结尾的最长长度,那么有状态转移方程:

fi=maxj[0,i)fj+1

其中要满足:

aimaj

ajmii

(因为只能有一个变的,且要所有可能的序列都满足,所以使条件最苛刻即可);

我们把转移顺序看作第一维,和剩下两维构成了一个三维偏序问题,所以用CDQ搞它即可;

注意这种题和普通的CDQ不同,我们分治时必须要把左边区间全部更新完在更新右边区间,并且每次回溯时要按转移顺序重新排序,因为回溯的时候还要继续递归;

这里应该就不能用归并排序了,因为没有统一的 cmp,所以老老实实的用 sort 吧(常数大了点,不过这道题没有关系);

具体细节看代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m;
struct sss{
	int a, mi, ma, id;
}e[500005];
inline bool cmp1(sss x, sss y) {
	return x.a < y.a;
}
inline bool cmp2(sss x, sss y) {
	return x.ma < y.ma;
}
inline bool cmp3(sss x, sss y) {
	return x.id < y.id;
}
int f[500005];
int ans;
namespace BIT{
	int tr[500005];
	inline int lowbit(int x) {
		return x & (-x);
	}
	void add(int pos, int d) {
		for (int i = pos; i <= 100000; i += lowbit(i)) {
			if (d == 0) tr[i] = d;
			else tr[i] = max(tr[i], d);
		}
	}
	int ask(int pos) {
		int an = 0;
		for (int i = pos; i; i -= lowbit(i)) {
			an = max(an, tr[i]);
		}
		return an;
	}
}
using namespace BIT;
void cdq(int l, int r) {
	if (l == r) {
		f[l] = max(f[l], 1);
		return;
	}
	int mid = (l + r) >> 1;
	cdq(l, mid);
	sort(e + l, e + mid + 1, cmp2);
	sort(e + mid + 1, e + r + 1, cmp1);
	int i = mid + 1;
	int j = l;
	while(i <= r) {
		while(j <= mid && e[i].a >= e[j].ma) {
			add(e[j].a, f[e[j].id]);
			j++;
		}
		f[e[i].id] = max(f[e[i].id], ask(e[i].mi) + 1);
		i++;
	}
	for (int i = l; i < j; i++) {
		add(e[i].a, 0);
	}
	sort(e + l, e + r + 1, cmp3); //注意排序;
	cdq(mid + 1, r); //先把左区间处理完再处理右区间;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> e[i].a;
		e[i].mi = e[i].a;
		e[i].ma = e[i].a;
		e[i].id = i;
	}
	int x, y;
	for (int i = 1; i <= m; i++) {
		cin >> x >> y;
		e[x].mi = min(e[x].mi, y);
		e[x].ma = max(e[x].ma, y);
	}
	cdq(1, n);
	for (int i = 1; i <= n; i++) ans = max(ans, f[i]);
	cout << ans;
	return 0;
}

To be continued...

posted @   Peppa_Even_Pig  阅读(56)  评论(16编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示