lxl 讲课的记录

D1

lxl:LCT 没有前途。所以平衡树一般只需要 fhq-treap。

线段树、平衡树简单例题

P3215

image

注意到抵消掉合法括号串之后一定是这样的情况:))))((((即前缀最小值 \(a\)、后缀最大值 \(b\)

答案即为 $\left \lceil a \right \rceil +\left \lceil b\right \rceil $

合并左右区间是容易的,区间染色和区间反转是容易的,翻转可以用平衡树。

P7706

image

观察到 \(\min B_j\) 可以改为枚举 \(B_j\),合并时讨论减去的在哪边即可。做 \(\min A_i-B_j\) 也是可以合并的。

// Problem: P7706 「Wdsr-2.7」文文的摄影布置
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/P7706
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// Author:British Union
// Long live UOB and koala
// 
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=5e5+5,INF=3e8;
#define ls (k<<1)
#define rs (k<<1|1)
#define mid ((l+r)>>1)
struct node{
	int am,bm,lab,rab,ans;
	node(){
		am=-INF,bm=INF,lab=-INF,rab=-INF,ans=-INF;
	}
}xds[maxn<<2];
node cur,emp;
int a[maxn],b[maxn];
node ret(int i){
	node w;w.am=a[i],w.bm=b[i];
	return w;
}
node merge(node a,node b){
	node c;
	c.am=max(a.am,b.am);c.bm=min(a.bm,b.bm);
	c.lab=max(max(a.lab,b.lab),b.am-a.bm);
	c.rab=max(max(a.rab,b.rab),a.am-b.bm);
	c.ans=max(max(a.ans,b.ans),max(b.am+a.rab,a.am+b.lab));
	return c;
}
void pushup(int k){
	xds[k]=merge(xds[ls],xds[rs]);
}
void modify(int k,int l,int r,int x){
	if(l==r){
		xds[k]=ret(l);
		return ;
	}
	if(x<=mid)modify(ls,l,mid,x);
	else modify(rs,mid+1,r,x);
	pushup(k);
}
void build(int k,int l,int r){
	if(l==r){
		xds[k]=ret(l);
		return ;
	}
	build(ls,l,mid);build(rs,mid+1,r);
	pushup(k);
}
void query(int k,int l,int r,int x,int y){
	if(x<=l&&r<=y){
		cur=merge(cur,xds[k]);
		return ;
	}
	if(x<=mid)query(ls,l,mid,x,y);
	if(mid<y)query(rs,mid+1,r,x,y);
}
int n,m;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)cin>>b[i];
	build(1,1,n);
	for(int i=1;i<=m;i++){
		int tp,x,y;cin>>tp>>x>>y;
		if(tp==1)a[x]=y,modify(1,1,n,x);
		if(tp==2)b[x]=y,modify(1,1,n,x);
		if(tp==3){
			cur=emp;
			query(1,1,n,x,y);
			cout<<cur.ans<<"\n";
		}
	}
	return 0;
}

P6617

序列,给定常数 \(w\)

1.单点修改

2.查询区间是否存在两个数和为 \(w\)

\(n,m\le 5\times 10^5,a_i,w\le 10^9\)

没有修改的话我知道每个数后继的第一个和它配对的数就可以了。但是要修改。

注意到支配的性质:如果 \(l\)\(r',r(r'<r)\) 都能配对,那么选 \(r\) 总不如选 \(r'\):它更可能满足条件。

然后我改变一个点只会对至多 \(5\) 个位置产生影响,直接维护即可。

// Problem: P6617 查找 Search
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/P6617
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// Author:British Union
// Long live UOB and koala
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
// #define int long long
const int maxn=5e5+5;
int xds[maxn<<2],nxt[maxn],a[maxn];
#define ls (k<<1)
#define rs (k<<1|1)
#define mid ((l+r)>>1)
void pushup(int k){
	xds[k]=max(xds[ls],xds[rs]);
}
void modify(int k,int l,int r,int x){
	if(l==r){
		xds[k]=nxt[x];
		return ;
	}
	if(x<=mid)modify(ls,l,mid,x);
	else modify(rs,mid+1,r,x);
	pushup(k);
}
int query(int k,int l,int r,int x,int y){
	if(x>y)return 0;
	if(x<=l&&r<=y)return xds[k];
	int res=0;
	if(x<=mid)res=max(res,query(ls,l,mid,x,y));
	if(mid<y)res=max(res,query(rs,mid+1,r,x,y));
	return res;
}
set<int> st[maxn];
int n,m,lst=0,w;
void calc(int x){
	// cout<<x<<" is calced"<<endl;
	if(x==0)return ;
	auto it1=st[a[x]].lower_bound(x),it2=st[w-a[x]].lower_bound(x);
	if(it2==st[w-a[x]].begin())nxt[x]=0;
	else if(it1==st[a[x]].begin())nxt[x]=(*--it2);
	else if(*--it1>*--it2)nxt[x]=0;
	else nxt[x]=*it2;
	modify(1,1,n,x);
}

int val[maxn];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m>>w;
	for(int i=0;i<=w;i++)st[i].insert(0);
	for(int i=1;i<=n;i++){
		cin>>a[i];
		st[a[i]].insert(i);
	}
	for(int i=1;i<=n;i++)calc(i);
	for(int i=1;i<=n;i++)modify(1,1,n,i);
	for(int i=1;i<=m;i++){
		int tp,x,y;
		cin>>tp>>x>>y;
		if(tp==1){
			bool c1=0,c2=0,c3=0,c4=0;
			auto it=st[w-a[x]].upper_bound(x);
			auto p1=it;
			if(it!=st[w-a[x]].end())c1=1;
			it=st[a[x]].upper_bound(x);
			auto p2=it;
			if(it!=st[a[x]].end())c2=1;
			st[a[x]].erase(x);a[x]=y;st[a[x]].insert(x);
			it=st[w-a[x]].upper_bound(x);
			auto p3=it;
			if(it!=st[w-a[x]].end())c3=1;
			it=st[a[x]].upper_bound(x);
			auto p4=it;
			if(it!=st[a[x]].end())c4=1;
			if(c1)calc(*p1);if(c2)calc(*p2);if(c3)calc(*p3);if(c4)calc(*p4);
			calc(x);
		}else{
			x^=lst,y^=lst;
			// cout<<x<<"-"<<y<<endl;
			int M=query(1,1,n,x,y);
			if(M>=x)cout<<"Yes\n",++lst;
			else cout<<"No\n";
		}
	}

	return 0;
}

P7739

你看这种正式比赛也会出垃圾题。——lxl。

可能确实挺垃圾的,不是很困难,但是比较可恶。

发现这是连分数

\[\frac{1}{\frac{1}{\frac{1}{\dots}+a_2}+a_1}+a_0 \]

容易发现,对于序列左侧加过去的 \(a\),造成的影响是(设原来是 \(\dfrac{x}{y}\)

\[\begin{bmatrix} x \\ y \end{bmatrix} \to \begin{bmatrix} ax+y\\ x \end{bmatrix} \iff \begin{bmatrix} a&1\\ 1&0 \end{bmatrix} \times \begin{bmatrix} x \\ y \end{bmatrix} \to \begin{bmatrix} x\\ y \end{bmatrix}\]

还有一种操作感觉很厉害,但不是这样。可以发现,这两个情况是等价的,🤡🤡🤡

还是维护矩阵。容易知道每个操作对应的矩阵乘法形式。每个节点维护翻转/反转或不的四种情况。

// Problem: P7739 [NOI2021] 密码箱
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/P7739
// Memory Limit: 1 MB
// Time Limit: 2000 ms
// Author:British Union
// Long live UOB and koala
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+5,mod=998244353;
struct mat{
	int a[2][2];
	mat(){a[0][1]=a[1][0]=0;a[0][0]=a[1][1]=1;}
}op[2],I;
void print(mat A){
	cout<<endl;
	for(int i=0;i<2;i++){
		for(int j=0;j<2;j++)cout<<A.a[i][j]<<" ";
		cout<<endl; 
	}
	cout<<endl;
}
mat operator *(const mat &a,const mat &b){
	mat c;c.a[0][0]=c.a[1][1]=0;
	for(int i=0;i<2;i++){
		for(int j=0;j<2;j++){
			for(int k=0;k<2;k++){
				(c.a[i][j]+=a.a[i][k]*b.a[k][j])%=mod;
			}
		}
	}
	// print(a);cout<<"*"<<endl;print(b);cout<<"="<<endl;print(c);cout<<endl;
	return c;
}
int sze[maxn],pri[maxn],l[maxn],r[maxn],cnt=0;
mat v[5][maxn];
bool val[maxn];
void pushup(int k){
	sze[k]=sze[l[k]]+sze[r[k]]+1;
	v[1][k]=v[1][l[k]]*op[val[k]]*v[1][r[k]];
	v[2][k]=v[2][r[k]]*op[val[k]]*v[2][l[k]];
	v[3][k]=v[3][l[k]]*op[val[k]^1]*v[3][r[k]];
	v[4][k]=v[4][r[k]]*op[val[k]^1]*v[4][l[k]];
}
bool tg1[maxn],tg2[maxn];
void REV(int k){
	if(!k)return ;
	tg1[k]^=1;
	swap(v[1][k],v[2][k]);swap(v[3][k],v[4][k]);
	swap(l[k],r[k]);
}
void FLIP(int k){
	if(!k)return ;
	tg2[k]^=1;val[k]^=1;
	// cout<<k<<" -> "<<tg2[k]<<endl;
	swap(v[1][k],v[3][k]);swap(v[2][k],v[4][k]);
}
void pushdown(int k){
	if(tg1[k]){
		REV(l[k]);REV(r[k]);tg1[k]=0;
	}
	if(tg2[k]){
		FLIP(l[k]);FLIP(r[k]);tg2[k]=0;
	}
}
void split(int now,int k,int &x,int &y){
	if(!now)x=y=0;
	else{
		pushdown(now);
		if(sze[l[now]]+1<=k)x=now,split(r[now],k-sze[l[now]]-1,r[now],y);
		else y=now,split(l[now],k,x,l[now]);
		pushup(now);
	}
}
int merge(int x,int y){
	if(!x||!y)return x+y;
	if(pri[x]<pri[y]){
		pushdown(x);
		r[x]=merge(r[x],y);
		pushup(x);
		return x;
	}else{
		pushdown(y);
		l[y]=merge(x,l[y]);
		pushup(y);
		return y;
	}
}
int n,m,rt=0;
void ins(bool tp){
	val[++cnt]=tp;
	sze[cnt]=1;pri[cnt]=rand();
	v[1][cnt]=v[2][cnt]=op[tp];
	v[3][cnt]=v[4][cnt]=op[tp^1];
	rt=merge(rt,cnt);
	// cout<<"After merging: "<<rt<<" "<<cnt<<endl;
	// print(v[1][rt]);
}
void calc(){
	int A=v[1][rt].a[0][1],B=v[1][rt].a[1][1];
	// print(v[1][rt]);
	A=(A%mod+mod)%mod,B=(B%mod+mod)%mod;
	cout<<B<<" "<<A<<endl;
}
char s[maxn];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	op[0].a[0][0]=op[0].a[0][1]=op[0].a[1][1]=1;op[0].a[1][0]=0;
	op[1].a[1][1]=2,op[1].a[1][0]=1,op[1].a[0][1]=mod-1;op[1].a[0][0]=0;
	// print(op[0]);print(op[1]);
	// print(op[0]*op[1]*op[0]*op[1]);
	cin>>n>>m;
	cin>>s+1;
	ins(0);
	for(int i=1;i<=n;i++){
		if(s[i]=='W')ins(0);
		else ins(1);
		// calc();
	}
	calc();
	for(int i=1;i<=m;i++){
		cin>>s+1;int l,r,x,y,z;
		if(s[1]=='A'){
			char a;cin>>a;
			if(a=='W')ins(0);
			else ins(1);
		}else if(s[1]=='F'){
			cin>>l>>r;
			split(rt,l,x,y);
			split(y,r-l+1,y,z);
			// print(v[1][y]);print(v[3][y]);
			FLIP(y);
			// cout<<"FLIPed "<<y<<endl;
			// print(v[1][y]);print(v[3][y]);
			rt=merge(x,merge(y,z));
		}else{
			cin>>l>>r;
			split(rt,l,x,y);
			split(y,r-l+1,y,z);
			REV(y);
			rt=merge(x,merge(y,z));
		}
		calc();
	}
	return 0;
}

颜色段均摊

特点:修改有区间染色操作。

用平衡树维护区间的颜色连续段。

区间染色每次最多只会增加 \(O(1)\) 个连续颜色段,用平衡树维护所有连续段即可。

均摊的颜色段插入删除次数 \(O(n+m)\)

image

课件中不明觉厉的图。

CF444C

image

模板题,维护区间和即可。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
#define int long long
#define ls (k<<1)
#define rs (k<<1|1)
#define mid ((l+r)>>1)
int n,m;
namespace ST{
	int xds[maxn<<2],add[maxn<<2];
	void ADD(int k,int l,int r,int v){
		add[k]+=v;xds[k]+=(r-l+1)*v;
	}
	void pushup(int k){
		xds[k]=xds[ls]+xds[rs];
	}
	void pushdown(int k,int l,int r){
		ADD(ls,l,mid,add[k]);ADD(rs,mid+1,r,add[k]);
		add[k]=0;
	}
	int query(int k,int l,int r,int x,int y){
		if(x<=l&&r<=y)return xds[k];
		int res=0;pushdown(k,l,r);
		if(x<=mid)res+=query(ls,l,mid,x,y);
		if(mid<y)res+=query(rs,mid+1,r,x,y);
		return res;
	}
	void modify(int k,int l,int r,int x,int y,int v){
		if(x<=l&&r<=y)return ADD(k,l,r,v);
		pushdown(k,l,r);
		if(x<=mid)modify(ls,l,mid,x,y,v);
		if(mid<y)modify(rs,mid+1,r,x,y,v);
		pushup(k);
	}
}
using namespace ST;
struct node{
	int l,r,col;
	node(int a=0,int b=0,int c=0){
		l=a,r=b,col=c;
	}
	bool operator <(const node &b)const{return l<b.l;}
};
set<node> S;
#define Iter set<node>::iterator
Iter split(int x){
	Iter it=S.lower_bound(node(x));
	if(it!=S.end()&&it->l==x)return it;
	--it;
	int c=it->col,L=it->l,R=it->r;
	S.erase(it);S.insert(node(L,x-1,c));
	return S.insert(node(x,R,c)).first;
}
void change(int l,int r,int x){
	auto ed=split(r+1),st=split(l);
	for(auto it=st;it!=ed;it++)modify(1,1,n,it->l,it->r,abs(x-it->col));
	S.erase(st,ed);S.insert(node(l,r,x));
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n+1;i++)S.insert(node(i,i,i));
	int op,l,r,v;
	while(m--){
		cin>>op>>l>>r;
		if(op==1){
			cin>>v;
			change(l,r,v);
		}else cout<<query(1,1,n,l,r)<<"\n";
	}
	return 0;
}

CF453E

给一个序列,每个位置有初值 \(a_i\),最大值 \(m_i\),这个值每秒会增大 \(r_i\),直到 \(m_i\)

\(m\) 时间升序询问,每次询问区间和,并将区间的所有数字变成 \(0\)

这里的颜色被认为是“开始时间”,多久从 \(0\) 开始,操作就是染色。同一个颜色段处理是二维数点,有些已经到 \(\max\) 了,有些还没有(计算每个点到 \(\max\) 的时间)。主席树即可。

P5066

image

中间四个操作会让区间中0/1的颜色段各自向左/右扩散一格的位置。

使用平衡树维护颜色段,打标记记下这些东西:

向左扩展的长度,向右扩展的长度,子树区间长度和(这样可以定位),左右端点(显然应该维护这个),子树长度最小值(我们要删除寄了的段)

口胡容易代码难

PKUSC2021D1T2

image

一个颜色段维护长度变化量,每一次会变化多少,子树区间长度和,子树最小长度,然后做楼房重建的平衡树版本即可。但是可惜我不会楼房重建,先鸽了。

P9061

image

\(10^6\)

发现这个操作是二维颜色段均摊,但是是特殊的,是从左下角开始的一个矩形。我们的颜色段就应该是一个“阶梯”和若干在阶梯上面的点。

每一次我们需要把阶梯的一部分和一部分不在阶梯上的点推上去。每个点只会被推一次,而每个点被推上去的时间是二维数点。观察到推不会影响相对顺序,平衡树维护即可。

询问怎么办?阶梯上面的是容易的。外面的分情况:在阶梯内部显然是 \(0\),否则可以用点的左边加上点的下面加上点上右面的点减去全部得到左下面的。上右面的点没有被更新,所以也是二维数点。就单 \(\log\) 完成了此题。

Hanghang 跳大神跳过了/bx

// Problem: P9061 [Ynoi2002] Optimal Ordered Problem Solver
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/P9061
// Memory Limit: 512 MB
// Time Limit: 4000 ms
// Author:British Union
// Long live UOB and koala
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
// #define int long long
namespace FastIO {
#if __cplusplus < 201400
#error Please use C++14 or higher.
#endif
#if __cplusplus > 201700
#define INLINE_V inline
#else
#define INLINE_V
#endif
#if (defined(LOCAL) || defined(_WIN32)) && !defined(DISABLE_MMAP)
#define DISABLE_MMAP
#endif
#ifndef DISABLE_MMAP
#include <sys/mman.h>
#endif
#ifdef LOCAL
inline char gc() { return getchar(); }
inline void pc(char c) { putchar(c); }
#else
#ifdef DISABLE_MMAP
INLINE_V constexpr int _READ_SIZE = 1 << 18;
INLINE_V static char _read_buffer[_READ_SIZE], *_read_ptr = nullptr,
                                               *_read_ptr_end = nullptr;
inline char gc() {
    if (__builtin_expect(_read_ptr == _read_ptr_end, false)) {
        _read_ptr = _read_buffer;
        _read_ptr_end =
            _read_buffer + fread(_read_buffer, 1, _READ_SIZE, stdin);
#ifdef CHK_EOF
        if (__builtin_expect(_read_ptr == _read_ptr_end, false)) return EOF;
#endif
    }
    return *_read_ptr++;
}
#else
INLINE_V static const char *_read_ptr =
    (const char *)mmap(nullptr, INT_MAX, 1, 2, 0, 0);
inline char gc() { return *_read_ptr++; }
#endif
INLINE_V constexpr int _WRITE_SIZE = 1 << 18;
INLINE_V static char _write_buffer[_WRITE_SIZE], *_write_ptr = _write_buffer;
inline void pc(char c) {
    *_write_ptr++ = c;
    if (__builtin_expect(_write_buffer + _WRITE_SIZE == _write_ptr, false)) {
        fwrite(_write_buffer, 1, _write_ptr - _write_buffer, stdout);
        _write_ptr = _write_buffer;
    }
}
INLINE_V struct _auto_flush {
    ~_auto_flush() {
        fwrite(_write_buffer, 1, _write_ptr - _write_buffer, stdout);
    }
} _auto_flush;
#endif
#ifdef CHK_EOF
inline bool _isdigit(char c) { return (c & 16) && c != EOF; }
inline bool _isgraph(char c) { return c > 32 && c != EOF; }
#else
inline bool _isdigit(char c) { return c & 16; }
inline bool _isgraph(char c) { return c > 32; }
#endif
template <class T>
INLINE_V constexpr bool _is_integer = numeric_limits<T>::is_integer;
template <class T>
INLINE_V constexpr bool _is_signed = numeric_limits<T>::is_signed;
template <class T>
INLINE_V constexpr bool _is_unsigned = _is_integer<T> && !_is_signed<T>;
template <>
INLINE_V constexpr bool _is_integer<__int128> = true;
template <>
INLINE_V constexpr bool _is_integer<__uint128_t> = true;
template <>
INLINE_V constexpr bool _is_signed<__int128> = true;
template <>
INLINE_V constexpr bool _is_unsigned<__uint128_t> = true;
#undef INLINE_V
inline void read(char &c) {
    do c = gc();
    while (!_isgraph(c));
}
inline void read_cstr(char *s) {
    char c = gc();
    while (!_isgraph(c)) c = gc();
    while (_isgraph(c)) *s++ = c, c = gc();
    *s = 0;
}
inline void read(string &s) {
    char c = gc();
    s.clear();
    while (!_isgraph(c)) c = gc();
    while (_isgraph(c)) s.push_back(c), c = gc();
}
#ifdef IN_HAS_NEG
template <class T, enable_if_t<_is_signed<T>, int> = 0>
inline void read(T &x) {
    char c = gc();
    bool f = true;
    x = 0;
    while (!_isdigit(c)) {
        if (c == 45) f = false;
        c = gc();
    }
    if (f)
        while (_isdigit(c)) x = x * 10 + (c & 15), c = gc();
    else
        while (_isdigit(c)) x = x * 10 - (c & 15), c = gc();
}
template <class T, enable_if_t<_is_unsigned<T>, int> = 0>
#else
template <class T, enable_if_t<_is_integer<T>, int> = 0>
#endif
inline void read(T &x) {
    char c = gc();
    while (!_isdigit(c)) c = gc();
    x = 0;
    while (_isdigit(c)) x = x * 10 + (c & 15), c = gc();
}
inline void write(char c) { pc(c); }
inline void write_cstr(const char *s) {
    while (*s) pc(*s++);
}
inline void write(const string &s) {
    for (char c : s) pc(c);
}
#ifdef OUT_HAS_NEG
template <class T, enable_if_t<_is_signed<T>, int> = 0>
inline void write(T x) {
    char buffer[numeric_limits<T>::digits10 + 1];
    int digits = 0;
    if (x >= 0) do
            buffer[digits++] = (x % 10) | 48, x /= 10;
        while (x);
    else {
        pc(45);
        do buffer[digits++] = -(x % 10) | 48, x /= 10;
        while (x);
    }
    while (digits) pc(buffer[--digits]);
}
template <class T, enable_if_t<_is_unsigned<T>, int> = 0>
#else
template <class T, enable_if_t<_is_integer<T>, int> = 0>
#endif
inline void write(T x) {
    char buffer[numeric_limits<T>::digits10 + 1];
    int digits = 0;
    do buffer[digits++] = (x % 10) | 48, x /= 10;
    while (x);
    while (digits) pc(buffer[--digits]);
}
template <int N>
struct _tuple_io_helper {
    template <class... T>
    static inline void _read(tuple<T...> &x) {
        _tuple_io_helper<N - 1>::_read(x), read(get<N - 1>(x));
    }
    template <class... T>
    static inline void _write(const tuple<T...> &x) {
        _tuple_io_helper<N - 1>::_write(x), pc(32), write(get<N - 1>(x));
    }
};
template <>
struct _tuple_io_helper<1> {
    template <class... T>
    static inline void _read(tuple<T...> &x) {
        read(get<0>(x));
    }
    template <class... T>
    static inline void _write(const tuple<T...> &x) {
        write(get<0>(x));
    }
};
template <class... T>
inline void read(tuple<T...> &x) {
    _tuple_io_helper<sizeof...(T)>::_read(x);
}
template <class... T>
inline void write(const tuple<T...> &x) {
    _tuple_io_helper<sizeof...(T)>::_write(x);
}
template <class T1, class T2>
inline void read(pair<T1, T2> &x) {
    read(x.first), read(x.second);
}
template <class T1, class T2>
inline void write(const pair<T1, T2> &x) {
    write(x.first), pc(32), write(x.second);
}
template <class T1, class... T2>
inline void read(T1 &x, T2 &...y) {
    read(x), read(y...);
}
template <class... T>
inline void read_cstr(char *x, T *...y) {
    read_cstr(x), read_cstr(y...);
}
template <class T1, class... T2>
inline void write(const T1 &x, const T2 &...y) {
    write(x), write(y...);
}
template <class... T>
inline void write_cstr(const char *x, const T *...y) {
    write_cstr(x), write_cstr(y...);
}
template <class T>
inline void print(const T &x) {
    write(x);
}
inline void print_cstr(const char *x) { write_cstr(x); }
template <class T1, class... T2>
inline void print(const T1 &x, const T2 &...y) {
    print(x), pc(32), print(y...);
}
template <class... T>
inline void print_cstr(const char *x, const T *...y) {
    print_cstr(x), pc(32), print_cstr(y...);
}
inline void println() { pc(10); }
inline void println_cstr() { pc(10); }
template <class... T>
inline void println(const T &...x) {
    print(x...), pc(10);
}
template <class... T>
inline void println_cstr(const T *...x) {
    print_cstr(x...), pc(10);
}
}
using namespace FastIO;
const int maxn=1e6+5;
int n;
struct BIT{
	int c[maxn];
	BIT(){memset(c,0,sizeof(c));}
	inline void add(int p,int x){
		for(;p<=n;p+=p&-p)c[p]+=x;
	}
	inline int query(int p){
		int res=0;
		for(;p;p-=p&-p)res+=c[p];
		return res;
	}
	inline int ask(int l,int r){
	    return query(r)-query(l-1);
	}
}B,Bt1,Bt2;
struct BIT2{
	int c[maxn];
	BIT2(){memset(c,0x3f3f3f3f,sizeof(c));}
	inline void add(int p,int x){
		p=n-p+1;
		for(;p<=n;p+=p&-p)c[p]=min(c[p],x);
	}
	inline int query(int p){
		int res=0x3f3f3f3f;p=n-p+1;
		for(;p;p-=p&-p)res=min(res,c[p]);
		return res;
	}
}B2;
struct BIT3{
	int c[maxn];
	BIT3(){memset(c,0,sizeof(c));}
	inline void add(int p,int x){
		p=n-p+1;
		for(;p<=n;p+=p&-p)c[p]=max(c[p],x);
	}
	inline int query(int p){
		int res=0;p=n-p+1;
		for(;p;p-=p&-p)res=max(res,c[p]);
		return res;
	}
}B3;
struct pt{
	int x,y;
	pt(int a=0,int b=0){x=a,y=b;}
	inline bool operator <(const pt &b)const{return x==b.x?y>b.y:x<b.x;}
	inline bool operator ==(const pt &b)const{return x==b.x&&y==b.y;}
};
inline bool operator <=(const pt &a,const pt &b){return a<b||a==b;}
mt19937 qsy(time(0));
struct node{
	int pri,l,r,sze,tg1,tg2;
	pt val;
	node(){
		tg1=tg2=-1;
	}
};
struct BST{
	node p[maxn];
	int x,y,z,rt,cnt;
	BST(){
		cnt=0;
	}
	inline void pushup(int x){
		p[x].sze=p[p[x].l].sze+p[p[x].r].sze+1;
	}
	inline void opr(int k,int tp,int v){
		if(!k)return ;
		if(tp==1)p[k].val.y=v,p[k].tg1=v;
		else p[k].val.x=v,p[k].tg2=v;
	}
	inline void pushdown(int k){
		if(p[k].tg1!=-1)opr(p[k].l,1,p[k].tg1),opr(p[k].r,1,p[k].tg1),p[k].tg1=-1;
		if(p[k].tg2!=-1)opr(p[k].l,2,p[k].tg2),opr(p[k].r,2,p[k].tg2),p[k].tg2=-1;
	}
	void split(int now,pt k,int &x,int &y){
		if(!now)x=y=0;
		else{
			pushdown(now);
			if(p[now].val<=k)x=now,split(p[now].r,k,p[now].r,y);
			else y=now,split(p[now].l,k,x,p[now].l);
			pushup(now);
		}
	}
	void splitx(int now,int k,int &x,int &y){
		if(!now)x=y=0;
		else{
			pushdown(now);
			if(p[now].val.x<=k)x=now,splitx(p[now].r,k,p[now].r,y);
			else y=now,splitx(p[now].l,k,x,p[now].l);
			pushup(now);
		}
	}
	void splity(int now,int k,int &x,int &y){
		if(!now)x=y=0;
		else{
			pushdown(now);
			if(p[now].val.y>k)x=now,splity(p[now].r,k,p[now].r,y);
			else y=now,splity(p[now].l,k,x,p[now].l);
			pushup(now);
		}
	}
	int merge(int x,int y){
		if(!x||!y)return x+y;
		if(p[x].pri<p[y].pri){
			pushdown(x);
			p[x].r=merge(p[x].r,y);
			pushup(x);
			return x;
		}else{
			pushdown(y);
			p[y].l=merge(x,p[y].l);
			pushup(y);
			return y;
		}
	}
	inline int newnode(pt v){
		p[++cnt].val=v,p[cnt].sze=1,p[cnt].pri=qsy();
		return cnt;
	}
	inline void ins(pt v){
		split(rt,v,x,y);
		rt=merge(x,merge(newnode(v),y));
	}
}F;
int m;
pair<int,int> a[maxn];
int per[maxn],o[maxn],xi[maxn],yi[maxn],Xi[maxn],Yi[maxn],ans[maxn],Int[maxn];
inline bool cmp(int x,int y){
	return Xi[x]<Xi[y];
}
inline bool cmp2(int x,int y){
	return xi[x]<xi[y];
}
vector<int> e[maxn];
signed main(){
// 	freopen("data.in","r",stdin);
// 	freopen("my.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	read(n,m);
	for(int i=1;i<=n;i++){
		read(a[i].first,a[i].second);
	}
	sort(a+1,a+n+1);
	bool sub=1;
	for(int i=1;i<=m;i++){
		read(o[i],xi[i],yi[i],Xi[i],Yi[i]);
		per[i]=i;
		if(o[i]!=1)sub=0;
	}
	sort(per+1,per+m+1,cmp);
	int now=n;
	for(int i=m;i>=1;i--){
		while(now>=1&&a[now].first>Xi[per[i]]){
			B.add(a[now].second,1);
			--now;
		}
		ans[per[i]]=B.ask(Yi[per[i]]+1,n);
	}
	now=m;
	sort(per+1,per+m+1,cmp2);
	for(int i=n;i>=1;i--){
		while(now>=1&&xi[per[now]]>=a[i].first){
			B2.add(yi[per[now]],per[now]);
			--now;
		}
		Int[i]=B2.query(a[i].second);
		if(Int[i]!=0x3f3f3f3f)e[Int[i]].push_back(i);
	}
	for(int i=1;i<=n;i++)Bt1.add(a[i].first,1),Bt2.add(a[i].second,1);
	int ct=0;
	for(int i=1;i<=m;i++){
		if(B3.query(xi[i]+1)<=yi[i]){
			F.splitx(F.rt,xi[i],F.x,F.y);
			int P=F.y;
			F.splity(F.x,yi[i],F.z,F.y);
			if(F.y)F.opr(F.y,o[i],o[i]==1?yi[i]:xi[i]);
			F.rt=F.merge(F.merge(F.z,F.y),P);
			B3.add(xi[i],yi[i]);
		}
		for(auto u:e[i]){
			Bt1.add(a[u].first,-1);
			Bt2.add(a[u].second,-1);
			if(o[i]==1)a[u].second=yi[i];
			else a[u].first=xi[i];
			ct++;
			F.ins(pt(a[u].first,a[u].second));
		}
		if(B3.query(Xi[i]+1)>Yi[i]){
			println(0);
		}else{
			ans[i]+=Bt1.query(Xi[i]);
			ans[i]+=Bt2.query(Yi[i]);
			F.splity(F.rt,Yi[i],F.x,F.y);
			F.splitx(F.y,Xi[i],F.y,F.z);
			ans[i]+=F.p[F.y].sze*2;
			ans[i]-=(n-(ct-F.p[F.y].sze));
			F.rt=F.merge(F.x,F.merge(F.y,F.z));
			println(ans[i]);
		}
	}
	
	return 0;
}

线段树均摊复杂度

UOJ228

先颜色段均摊再线段树维护。

但暴力根号可能会被:

\[(3,4,3,4,\dots)\to (1,2,1,2\dots,)\to (3,4,3,4,\dots) \]

卡爆。

维护区间最值。如果极差开根后没有变化显然可以直接区间减,就避免了这样的情况。否则暴力做就好了。

CF679E

image

加的数非负。

先颜色段均摊,考虑维护每一个值直到下一个 \(42\) 次幂的值。如果有搞出来爆的就暴力修改就可以。

D2

我们的楼叉楼先生偶染微恙(从北京带过来的病毒),然后做出了七天不完全康复的壮举(lxs 被感染后两天康复),生病了还 1:30 上知乎,不好说。

一般扫描线

CF1000F

对于一个值 a 出现的位置

...a..a....a...
   i  j    k

发现 \(j\) 会使得 \(l\in(i,j],r\in [j,k)\) 合法。此时我把 \(l\) 看成二维平面上的横坐标, \(r\) 看成二维平面上的纵坐标,就得到了一个 \(\texttt{4-side}\) 矩形,查询单点查。

我们得到了这样的问题:先矩形加,再单点查询。这是可以对某一维扫描线解决的。

TEST_73

发现是树上,那么没有“没用的边”,即不能造成连通块减一的边,因为不管怎么样一定不会连出环。

那么问题就是点减编号、端点都在 \([l,r]\) 内的边。看起来是不好做的,再好也只能 \(\log^2\)。但是注意到这三者(编号,两个端点) \(a,b,c\) 都适用一个限制,那么转化为 \(l\le\min(a,b,c)\le \max(a,b,c)\le r\),而这是好做的,类似于上一道题。

UOJ 637. [美团杯2021] A. 数据结构

给一个长为 \(n\) 的序列,\(m\) 次查询:
如果将区间 \([l,r]\) 中所有数都 \(+1\),那么整个序列有多少个不同的数?
询问间独立,也就是说每次查询后这个修改都会被撤销。

不如统计没出现的数(只能出现 \(0\) 次,只有一种情况!)。就是:原来出现了的数必须加一,加一的必须没出现,对每个值考虑就得到了 \(O(n)\) 个矩形。

Hdu5603

\(n\) 个区间;

\(m\) 次询问,每次询问给出一些点,求对于这些点而言,有多少个初始给定的区间包含了至少这些点中的一个点

\(n,m\),总点数 \(\le 3\times 10^5\)

每次询问统计没出现的区间,就得到了共 \(O(\Sigma)\) 个矩形,这是二维数点。

区间子区间问题

将每个区间看做二维平面上的点,区间的子区间转换为查询一个矩形内有多少点满足某条件。

由于区间 \([l,r]\) 满足 \(l\le r\),所以这里实际上是一个三角部分。

可以看做是一个 \(\texttt{2-side}\) 矩形。

问题变为查询 \(y\) 轴上的一个区间从 \(x=1\) 到当前 \(x\) 这段时间中,总共有多少个位置满足条件;

于是使用一个数据结构维护历史信息即可。

可以认为是每个位置 \(i\) 当前是否合法的值为 \(a_i\)

给每个位置 \(i\) 额外引入一个计数器 \(b_i\)(历史版本和)。

每次扫描线向右移动时,进行一个全局 \(b_i+a_i\to b_i\) 的操作。

或者对于线段树每个节点维护上次更新时间也能做。

CF526F Pudding Monsters

这里 \(k=1,2,\dots,n\)

每一行每一列恰有一个棋子,我们把每一列的那个棋子的位置(高度?)变成一维数组。然后就变成了有多少区间 \(\max-\min=r-l+1\)

注意到左边总是大于等于右边,扫描线时线段树求 \(\min\)\(\min\) 的个数即可。显然,用不着使用吉司机线段树。

Codeforces GYM 103069G EC final2020 G

给定一个序列,区间询问有多少子区间,其内部出现过的颜色数为奇数。

显然这相当于区间异或 \(1\),历史版本和,这是容易维护的。

P8868 比赛

这是很唐的,可以看 “线段树相关的小作品” 一节。

"换维扫描线"

P7560 JOISC2021 食品区

对序列扫描线,线段树维护时间维。麻烦的是有若干次清空不好搞。但发现只有最后一次清空是有效的。我们又发现前缀和最小值是最后一次清空(如果有),线段树上二分即可。

大概意思是换了另一个可选的维度。

D3

莫队

P4396

莫队,然后 \(O(1)-O(\sqrt n)\) 值域分块即可。

CF617E

前缀和,然后直接莫队。

P5268

差分一下询问然后莫队。

P4689

拍上 dfn 序,换根是假的(只用变换下区间)。然后上一道题。

// Problem: P4689 [Ynoi2016] 这是我自己的发明
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/P4689
// Memory Limit: 512 MB
// Time Limit: 1500 ms
// Author:British Union
// Long live UOB and koala
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
// #define int long long
const int maxn=5e5+5;
int dep[maxn],sze[maxn],son[maxn],to[maxn],fa[maxn],seg[maxn],rev[maxn];
vector<int> e[maxn];
void dfs1(int u,int f){
	fa[u]=f,sze[u]=1,dep[u]=dep[f]+1;
	for(auto v:e[u]){
		if(v==f)continue;
		dfs1(v,u);
		if(sze[v]>sze[son[u]])son[u]=v;
		sze[u]+=sze[v];
	}
}
void dfs2(int u,int t){
	to[u]=t;seg[u]=++seg[0];rev[seg[0]]=u;
	if(son[u])dfs2(son[u],t);
	for(auto v:e[u]){
		if(v!=fa[u]&&v!=son[u])dfs2(v,v);
	}
}
int FFind(int u,int v){
	while(dep[to[v]]>dep[u]+1)v=fa[to[v]];
	if(dep[to[v]]==dep[u]+1)return to[v];
	return rev[seg[u]+1];
}
int Find(int u,int v){
// 	cout<<"Find("<<u<<","<<v<<")="<<FFind(u,v)<<endl;
	return FFind(u,v);
}
int bel[maxn];
long long Ans[maxn];
struct query{
	int l,r,tp,id;
	bool operator <(const query &b)const{return bel[l]==bel[b.l]?r<b.r:bel[l]<bel[b.l];}
}q[maxn*10];
int cnt=0,n,m,rt=1,B,a[maxn],V=0;
long long ans=0;
map<int,int> mp;
int cl[maxn],cr[maxn],tp[maxn];
void addl(int v){
	cl[v]++;
	ans+=cr[v];
}
void addr(int v){
	cr[v]++;
	ans+=cl[v];
}
void dell(int v){
	cl[v]--;
	ans-=cr[v];
}
void delr(int v){
	cr[v]--;
	ans-=cl[v];
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	B=sqrt(n);
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<n;i++){
		int u,v;cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(1,0);
	dfs2(1,1);
	for(int i=1;i<=n;i++){
		bel[i]=(i/B);
		if(!mp.count(a[i]))mp[a[i]]=++V;
		a[i]=mp[a[i]];
	}
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>tp[i];
		if(tp[i]==1){
			cin>>x;
			rt=x;
		}else{
			cin>>x>>y;
			vector<pair<int,int> > v1,v2;
			if(x==rt) v1.push_back({n,1});
			else if(seg[x]<=seg[rt]&&seg[rt]<=seg[x]+sze[x]-1){
				int u=Find(x,rt);
				v1.push_back({seg[u]-1,1});
				v1.push_back({n,1});
				v1.push_back({seg[u]+sze[u]-1,-1});
			}else{
				v1.push_back({seg[x]-1,-1});v1.push_back({seg[x]+sze[x]-1,1});
			}
			if(y==rt) v2.push_back({n,1});
			else if(seg[y]<=seg[rt]&&seg[rt]<=seg[y]+sze[y]-1){
				int u=Find(y,rt);
				v2.push_back({seg[u]-1,1});
				v2.push_back({n,1});
				v2.push_back({seg[u]+sze[u]-1,-1});
			}else{
				v2.push_back({seg[y]-1,-1});v2.push_back({seg[y]+sze[y]-1,1});
			}
			for(auto u:v1){
				for(auto v:v2){
					q[++cnt]={u.first,v.first,u.second*v.second,i};
					// cout<<i<<" went to "<<q[cnt].l<<" "<<q[cnt].r<<" "<<q[cnt].tp<<endl;
				}
			}
		}
	}
	sort(q+1,q+cnt+1);
	int l=0,r=0;
	for(int i=1;i<=cnt;i++){
		while(r<q[i].r)addr(a[rev[++r]]);
		while(l>q[i].l)dell(a[rev[l--]]);
		while(r>q[i].r)delr(a[rev[r--]]);
		while(l<q[i].l)addl(a[rev[++l]]);
		// cout<<q[i].l<<" "<<q[i].r<<" = "<<ans<<endl;
		Ans[q[i].id]+=ans*q[i].tp;
	}
	for(int i=1;i<=m;i++)if(tp[i]==2)cout<<Ans[i]<<"\n";
	return 0;
}

P3245

后缀和,然后发现可以去掉十的若干次幂(除了 \(p=2,5\),此时容易处理),然后莫队。

// Problem: P3245 [HNOI2016] 大数
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/P3245
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// Author:British Union
// Long live UOB and koala
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+5;
int a[maxn],n,mod,m,V=0;
char s[maxn];
map<int,int> mp;
struct BIT{
	int c[maxn];
	void add(int p,int v){
		for(;p<=n;p+=p&-p)c[p]+=v;
	}
	int query(int p){
		int res=0;
		for(;p;p-=p&-p)res+=c[p];
		return res;
	}
	int ask(int l,int r){
		return query(r)-query(l-1);
	}
}A,B;
int t[maxn],ans=0,Ans[maxn],bel[maxn],Bl;
struct query{
	int l,r,id;
	bool operator <(const query &b)const{return bel[l]==bel[b.l]?r<b.r:bel[l]<bel[b.l];}
}q[maxn];
void add(int p){
	t[a[p]]++;
	ans+=t[a[p]];
}
void del(int p){
	ans-=t[a[p]];
	t[a[p]]--;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>mod>>s+1;
	n=strlen(s+1);
	cin>>m;
	Bl=n/sqrt(m);
	if(Bl==0)Bl=1;
	if(mod==2){
		for(int i=1;i<=n;i++){
			if((s[i]-'0')%2==0)A.add(i,i),B.add(i,1);
		}
		for(int i=1;i<=m;i++){
			int l,r;cin>>l>>r;
			cout<<A.ask(l,r)-B.ask(l,r)*(l-1)<<endl;
		}
		return 0;
	}
	if(mod==5){
		for(int i=1;i<=n;i++){
			if((s[i]-'0')%5==0)A.add(i,i),B.add(i,1);
		}
		for(int i=1;i<=m;i++){
			int l,r;cin>>l>>r;
			cout<<A.ask(l,r)-B.ask(l,r)*(l-1)<<endl;
		}
		return 0;
	}
	int z=1;
	for(int i=n;i>=1;i--){
		a[i]=(a[i+1]+(s[i]-'0')*z)%mod;
		z=z*10%mod;
		bel[i]=i/Bl;
	}
	for(int i=1;i<=n;i++){
		if(!mp.count(a[i]))mp[a[i]]=++V;
		a[i]=mp[a[i]];
	}
	a[n+1]=mp[0];
	// for(int i=0;i<=n;i++){
		// cout<<"a["<<i<<"]="<<a[i]<<endl;
	// }
	for(int i=1;i<=m;i++){
		q[i].id=i;
		cin>>q[i].l>>q[i].r;q[i].r++;
	}
	sort(q+1,q+m+1);
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		while(r<q[i].r)add(++r);
		while(l>q[i].l)add(--l);
		while(r>q[i].r)del(r--);
		while(l<q[i].l)del(l++);
		Ans[q[i].id]=ans-(q[i].r-q[i].l+1);
	}
	for(int i=1;i<=m;i++)cout<<Ans[i]<<"\n";
	return 0;
}

P3604

条件等价于至多有一个字母出现了奇数次。发现字母很少,压位i即可,即判断有没有 \(s_r\texttt{ xor }s_l=2^k\)。这是容易的。

P5906

回滚莫队(不删/不加),处理每块时把 \(l\) 置为块右端点/块左端点,\(r\) 置为块右端点/序列右端点,每个询问先移动 \(r\),再移动 \(l\),回答询问,再撤销 \(l\)(只要不是均摊复杂度)

P5386

回滚莫队,使用一个 \(O(1)-O(\sqrt n)\) 的分块。具体来说,维护一块的首尾相连的长和中间的 \(len(len-1)\) 和。

根号分治

P9809

根号分治模数,如果小于根号预处理,大于根号枚举商。用一个 \(O(\sqrt n)-O(1)\) 值域分块。

P9060

对质数大小根号分治,小的前缀和,大的莫队。

D4

根号分治

P5071

对质数大小根号分治,小的前缀和,大的莫队。

P6779

最小具有支配性:父亲会使得儿子没贡献。更弱地,跳到已访问的点会使得这个点失效。

编号分块。每一块有效点暴力往上跳(均摊 \(O(n)\))。散块就暴力处理,因为可能重新有效。所以需要记录一个点往上跳了多少次,整块向上了多少次。树剖求 \(k\) 级祖先是均摊复杂度正确的。

P7446/CF1491H

编号分块。维护一个点向前出块的第一个到达点。发现一个块至多 \(O(\sqrt n)\) 次就会使得此值为 \(a_i\)。因此可以暴力修改。

求 LCA 是容易的。

P5063

线段树不同的长度只有 \(O(\log)\) 个。

对每一个长度(区间加区间 \(\le a\)),发现复杂度是等比数列是 \(O(m\sqrt {m\log n})\)

树套树或 cdq 分治

P9068

考虑值的贡献:\(a>b\)\(a\) 第一次出现小于 \(b\) 最后一次出现位置。

再加个时间维就是三维偏序。

P4690

考虑没有修改。维护每个值前一次出现位置。就是区间小于 \(l\) 的数。树套树。

单点修改当然也可以。

区间修改发现是颜色段均摊,每个颜色段中的值指向它前面一个(除了开头)。那就可以做了。

LOJ6120

询问一个矩形和多少已知矩形有交。

容斥,斥掉在上下左右的,加上在左上右上左下右下的,三维偏序问题。

P3242

反过来考虑,一个盘子产生贡献的条件等价于水果端点 dfn 序在某区间中,

问题转化为询问一个点,覆盖它的矩形的权值第 \(k\) 大。

扫描线,线段树套权值线段树,询问时在此位置到根的所有线段树上同时二分。

D5

可持久化

P5795

讲的时候在调电脑,不会

LOJ6144

建出可持久化 Trie 树。

Xor 是容易处理的。打一个交换左右子树的标记即可。

And 和 Or 的效果是:把每某几层的左/右儿子合并进对面。发现此时暴力重构均摊复杂度是正确的(\(\log^2\))。

P2839

考虑离线做怎么做:二分答案,大于 \(mid\) 的数赋为 \(1\),否则 \(-1\)。即求左端点在 \([a,b]\) 之间,右端点在 \([c,d]\) 之间的最大子段和是否是非负值。就是 \([b,c]\) 的最大子段和加上 \([a,b)\) 的最大后缀和和 \((c,d]\) 的最大前缀和。这里发现 \(mid\) 只有 \(n\) 个本质不同的。预处理扫描线 \(mid\) 即可。

在线把预处理变成可持久化线段树就可以了。

P4592

每个点从父亲搞可持久化 Trie,差分即可。

P3567

绝对众数在每一位上都一定是众数。求出每一位哪个更多之后判存不存在即可。

CF543E(弱化版:不卡空间)

发现就是区间加区间 min 的主席树。

标记永久化或者标记下传时复制下即可。

原题严重卡空间

树上问题

CF383C

修改变成单点修改 \(val\times (-1)^{dep}\) ,查询变成根到这个点的链和即可。

hdu5692

唐氏题。

LOJ 6276

把路径两点 dfn 序当成坐标,发现一个颜色 ban 掉的就是若干个矩形。由于每个颜色出现次数很小,矩形不多,扫描线求交即可。

P1600&P2680&4216

典。

BZOJ3159

树剖+平衡树,每次把区间重链+轻边取出来翻一遍放回去,\(\log^2\)

CF536E

树剖,离线扫描线+线段树。

未公开题目

给一棵二叉树,每个点有一个条件,条件形如 <a,>a,a 是整数

每次询问从一个点 x 开始,初始有一个数 y,如果 y 满足 x 节点的条件,则 y 走到 x 的左儿子,否则走到 x 的右儿子

每次还可能修改一个点的 a 和对应的条件

求最后走到哪个点

走到 \(x\) (或者更下面)的条件是一段区间的并,是区间。

线段树维护 dfn 序点的区间并。

每次询问在重链上线段树二分,轻边判断。

\(O(n\log^2n)\)

D6

树上问题

P4211

扫描线,每个点到根路径加一,询问一个点到根和即可。

CF757G

只需要求出 LCA 深度和。换成可持久化线段树即可。交换操作暴力重构那两棵树。

卡空间。

CF1017G

如果没有染白,一个点染黑打标记,每次查询 \(val+dep\) 最大值。但这样好像很难染白。

变成一个点单点加 \(1\)(初始全部 \(-1\)),询问最大后缀和是否大于 \(-1\)

染白的操作就直接查询这个点值,如果是正的,就在这个地方减去。

CF696E

维护链最小值,均摊复杂度,每个只会被删除一次。

P5314

首先有 simple 的根号分治。

维护每个点的轻儿子平衡树,重儿子只有一个。

查询只有 \(\log\) 个轻儿子修改。

点分治和其他

T313496

扫描线时顺便维护即可。也可以分治

T313497

扫描线时维护一车东西

P7880

// Problem: P7880 [Ynoi2006] rldcot
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/P7880
// Memory Limit: 512 MB
// Time Limit: 500000 ms
// Author:British Union
// Long live UOB and koala
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5,N=1e5+5;
long long dep[N];
int sze[N],son[N],fa[N];
vector<pair<int,int> > e[N];
vector<int> Rev[N];
int n,m,cnt=0;
int L[maxn],R[maxn];
bool vis[N];
void dfs1(int u,int f){
	fa[u]=f,sze[u]=1;
	for(auto E:e[u]){
		int v=E.first,w=E.second;
		if(v==f)continue;
		dep[v]=dep[u]+w;
		dfs1(v,u);
		sze[u]+=sze[v];
		if(sze[v]>sze[son[u]])son[u]=v;
	}
}
map<long long,int> mp;
int V=0;
struct mat{
	int lx,rx,ly,ry;
	mat(int a=0,int b=0,int c=0,int d=0){
		lx=a,rx=b,ly=c,ry=d;
	}
	bool operator <(const mat &b)const{return rx==b.rx?ly<b.ly:rx>b.rx;}
}Re[N<<5];
vector<mat> em[N];
set<int> st;
vector<int> cur;
void dfs2(int u){
	cur.push_back(u);
	for(auto E:e[u])if(E.first!=fa[u])dfs2(E.first);
}
void dfs(int u,int del){
	for(auto E:e[u]){
		int v=E.first;
		if(v==son[u]||v==fa[u])continue;
		dfs(v,1);
	}
	if(son[u])dfs(son[u],0);
	for(auto E:e[u]){
		int v=E.first;
		if(v==son[u]||v==fa[u])continue;
		cur.clear();
		dfs2(v);
		for(auto r:cur){
			auto it=st.lower_bound(r);
			if(it!=st.end()){
				int suf=*it;
				em[dep[u]].push_back(mat(1,r,suf,n));
			}
			if(it!=st.begin()){
				--it;
				int pre=*it;
				em[dep[u]].push_back(mat(1,pre,r,n));
			}
		}
		for(auto r:cur)st.insert(r);
	}
	st.insert(u);
	if(del){
		st.clear();
	}
}
struct BIT{
	int c[N],S;
	void add(int p,int x){
		for(;p<=n;p+=p&-p)c[p]+=x;
	}
	int ask(int p){
		int res=0;
		for(;p;p-=p&-p)res+=c[p];
		return res;
	}
	void modify(int l,int r,int v){
		add(l,v);add(r+1,-v);
	}
}A;
vector<int> reva[N];
int Ans[maxn];
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
void write(int X)
{
	if(X<0){putchar('-');X=~(X-1);}int s[20],o=0;
	while(X){s[++o]=X%10;X/=10;}
	if(!o)s[++o]=0;while(o)putchar(s[o--]+'0');
	putchar('\n');
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	n=read(),m=read();
	for(int i=1;i<n;i++){
		int x,y,w;x=read(),y=read(),w=read();
		e[x].push_back({y,w});
		e[y].push_back({x,w});
	}
	dfs1(1,0);
	for(int i=1;i<=n;i++){
		if(!mp[dep[i]])mp[dep[i]]=++V;
		dep[i]=mp[dep[i]];
	}
	for(int i=1;i<=n;i++){
		em[dep[i]].push_back(mat(1,i,i,n));
	}
	dfs(1,0);
	for(int i=1;i<=m;i++){
		L[i]=read(),R[i]=read();
		if(L[i]>R[i])swap(L[i],R[i]);
		reva[L[i]].push_back(i);
	}
	for(int i=1;i<=V;i++){
		sort(em[i].begin(),em[i].end());
		int lst=n;
		for(auto u:em[i]){
			if(vis[u.rx])continue;
			vis[u.rx]=1;
			if(u.ly>lst)continue;
			Re[++cnt]=mat(1,u.rx,u.ly,lst);
			lst=u.ly-1;
		}
		for(auto u:em[i])vis[u.rx]=0;
	}
	for(int i=1;i<=cnt;i++){
		Rev[Re[i].rx].push_back(i);
		A.modify(Re[i].ly,Re[i].ry,1);
	}
	for(int i=1;i<=n;i++){
		for(auto u:reva[i]){
			Ans[u]=A.ask(R[u]);
		}
		for(auto u:Rev[i]){
			A.modify(Re[u].ly,Re[u].ry,-1);
		}
	}
	for(int i=1;i<=m;i++)write(Ans[i]);
	return 0;
}

P4149

直接点分治

CF600E

直接 Dsu on tree。

CF741D

直接点分治或者 dsu。

P6626

直接点分治

P3292

直接点分治

D7

P4475

随机数据,直接 KDT。

// Problem: P4475 巧克力王国
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4475
// Memory Limit: 500 MB
// Time Limit: 2500 ms
// UOB Koala
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=5e4+5;
int n,now,m,rt;
int a,b,c;
struct data{
	int d[2],mx[2],mn[2],ls,rs,val,sum;
	bool operator <(const data &b)const{return d[now]<b.d[now];}
}dat[maxn],t[maxn];
void pushup(int x){
	int ls=t[x].ls,rs=t[x].rs;
	for(int i=0;i<2;i++){
		t[x].mn[i]=t[x].mx[i]=t[x].d[i];
		if(ls)t[x].mn[i]=min(t[x].mn[i],t[ls].mn[i]),t[x].mx[i]=max(t[x].mx[i],t[ls].mx[i]);
		if(rs)t[x].mn[i]=min(t[x].mn[i],t[rs].mn[i]),t[x].mx[i]=max(t[x].mx[i],t[rs].mx[i]);
	}
	t[x].sum=t[ls].sum+t[rs].sum+t[x].val;
}
#define mid ((l+r)>>1)
int build(int l,int r,int d){
	now=d;
	nth_element(dat+l,dat+mid,dat+r+1);
	t[mid]=dat[mid];
	if(l<mid)t[mid].ls=build(l,mid-1,d^1);
	if(mid<r)t[mid].rs=build(mid+1,r,d^1);
	pushup(mid);
	return mid;
}
bool check(int x,int y){
	return a*x+b*y<c;
}
int query(int x){
	int cnt=0;
	cnt+=check(t[x].mn[0],t[x].mn[1]);
	cnt+=check(t[x].mx[0],t[x].mn[1]);
	cnt+=check(t[x].mn[0],t[x].mx[1]);
	cnt+=check(t[x].mx[0],t[x].mx[1]);
	if(cnt==0)return 0;
	if(cnt==4)return t[x].sum;
	int res=0;
	if(check(t[x].d[0],t[x].d[1]))res+=t[x].val;
	if(t[x].ls)res+=query(t[x].ls);
	if(t[x].rs)res+=query(t[x].rs);
	return res;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>dat[i].d[0]>>dat[i].d[1]>>dat[i].val;
	rt=build(1,n,0);
	while(m--){
		cin>>a>>b>>c;
		cout<<query(rt)<<"\n";
	}
	return 0;
}

P3710

直接 KDT。

P8528

直接点分治+支配点对。

CF765F

直接支配点对(有效的一个点的前驱 \(O(\log n)\))。

P9058

直接支配点对+点分治。

// Problem: P9058 [Ynoi2004] rpmtdq
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P9058
// Memory Limit: 512 MB
// Time Limit: 2000 ms
// UOB Koala
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+5,INF=1e15;
bool vis[maxn];
vector<pair<int,int> > e[maxn];
#define PI pair<int,int>
int dfs(int u,int fa){
	if(vis[u])return 0;
	int res=1;
	for(auto E:e[u]){
		int v=E.first;
		if(v!=fa)res+=dfs(v,u);
	}
	return res;
}
int Find(int u,int fa,int tot,int &zx){
	if(vis[u])return 0;
	int mx=0,sum=1;
	for(auto E:e[u]){
		int v=E.first;
		if(v==fa)continue;
		int t=Find(v,u,tot,zx);
		mx=max(mx,t),sum+=t;
	}
	if(max(mx,tot-sum)<=tot/2)zx=u;
	return sum;
}
struct pt{
	int u,v,w;
	bool operator <(const pt &b)const{return v<b.v;}
}p[maxn<<5];int V=0;
PI cur[maxn];
int st[maxn],tp=0;
void add(int u,int fa,int d,int &tot){
	if(vis[u])return;
	cur[++tot]={u,d};
	// cerr<<"{"<<u<<","<<d<<"} is finded"<<endl;
	for(auto E:e[u]){
		int v=E.first,w=E.second;
		if(v!=fa)add(v,u,d+w,tot);
	}
}
void solve(int u){
	if(vis[u]==1)return ;
	Find(u,0,dfs(u,0),u);
	// cerr<<"solving "<<u<<endl;
	vis[u]=1;int tot=0;
	for(auto E:e[u]){
		int v=E.first,w=E.second;
		add(v,u,w,tot);
	}tp=0;
	cur[++tot]={u,0};
	sort(cur+1,cur+tot+1);
	for(int i=1;i<=tot;i++){
		while(tp&&cur[st[tp]].second>cur[i].second)--tp;
		if(tp){
			int zfy=cur[st[tp]].first,qsy=cur[i].first,d=cur[i].second+cur[st[tp]].second;
			if(zfy>qsy)swap(zfy,qsy);
			p[++V]={zfy,qsy,d};
		}
		st[++tp]=i;
	}
	tp=0;
	reverse(cur+1,cur+tot+1);
	for(int i=1;i<=tot;i++){
		while(tp&&cur[st[tp]].second>cur[i].second)--tp;
		if(tp){
			int zfy=cur[st[tp]].first,qsy=cur[i].first,d=cur[i].second+cur[st[tp]].second;
			if(zfy>qsy)swap(zfy,qsy);
			p[++V]={zfy,qsy,d};
		}
		st[++tp]=i;
	}
	for(auto E:e[u])solve(E.first);
}
struct query{
	int l,r,id;
	bool operator <(const query &b)const{return r<b.r;}
}q[1000005];
int n,m,ans[1000005];
struct BIT{
	int c[maxn];
	BIT(){
		for(int i=0;i<maxn;i++)c[i]=INF;
	}
	void add(int p,int v){
		p=n-p+1;
		for(;p<=n;p+=p&-p)c[p]=min(c[p],v);
	}
	int query(int p){
		p=n-p+1;
		int res=INF;
		for(;p;p-=p&-p)res=min(res,c[p]);
		return res;
	}
}A;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v,w;cin>>u>>v>>w;
		e[u].push_back({v,w});
		e[v].push_back({u,w});
	}
	cin>>m;
	for(int i=1;i<=m;i++){
		cin>>q[i].l>>q[i].r;
		q[i].id=i;
	}
	solve(1);
	sort(p+1,p+V+1);sort(q+1,q+m+1);
	// for(int i=1;i<=V;i++){
		// cerr<<p[i].u<<" "<<p[i].v<<" "<<p[i].w<<" is calced through dfz"<<endl;
	// }
	int now=1;
	for(int i=1;i<=m;i++){
		while(now<=V&&p[now].v<=q[i].r){
			A.add(p[now].u,p[now].w);
			now++;
		}
		ans[q[i].id]=A.query(q[i].l);
	}
	for(int i=1;i<=m;i++){
		if(ans[i]==INF)ans[i]=-1;
		cout<<ans[i]<<"\n";
	}
	return 0;
}

P5428

找出相交线段即可。

相交相当于改变 \(y\) 轴相对大小,可以在一些点统计这个。

对点的 \(x\) 坐标扫描线,set 维护当前线段(的相对大小),发现一个线段加入和删除的时候随便算下可能相交的线即可。

posted @ 2023-11-23 15:17  British_Union  阅读(68)  评论(1编辑  收藏  举报