杂题之ds(1-10)

\(1\)\(P4062\)
https://www.luogu.com.cn/problem/P4062
题解:枚举众数,区间\([l+1,r]\)众数为\(x\),等价于\(S(x,r)-S(x,l)>r-l-(S(x,r)-S(x,l))\)
即要对于每个\(r\),统计有多少\(l\in [0,r-1]\),有\(2*S(x,r)-r>2*S(x,l)-l\),用树状数组维护\(2*S(x,i)-i\)即可。
但若对于每个众数均维护一次,复杂度到\(O(n^2)\),考虑优化。

0 1 2 3 4 5 6 7 8
0 0 0 1 0 0 1 0 1
0 -1 -2 -1 -2 -3 -2 -3 -2

上表格第一行为下标,第二行为原序列,第三行为树状数组维护序列,观察规律。
\(a_i\)为众数,则\(p_i=p_{i-1}+1\),否则\(p_i=p_{i-1}-1\)
换句话说,由连续\(0\)构成的连续段,成公差为\(-1\)的等差数列。
考虑用数据结构维护权值出现次数\(c_i\)
发现段内是无贡献的,所以可以以段为单位,同一进行操作。
加入连续值域\([x,y]\),即对区间\([x,y]\)\(1\)即可。
查询连续值领的贡献\([x,y]\),即\(\sum_{i=x}^{y}\sum_{j=1}^{i-1}c_j\)
\(S1_i=\sum_{k=1}^{i}c_k\)\(S2_i=\sum_{k=1}^{i}S1_k\)
则贡献为\(\sum_{i=x}^{y}S1_{i-1}=S2_{y-1}-S2_{x-2}\)
则若维护\(c\)的差分数组,则问题等价于单点加,并求三维前缀和,用树状数组维护。
若维护\(S1\),则问题等价于区间加等差数列,并求前缀和,用线段树维护。

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N=1e6+10;

int n,T;
vector<int> q[N];

struct SegmentTree{
	struct Node{
		int l,r;
		int s,fir,tol;
	}tr[4*N];
	void pushup(int u){
		tr[u].s=tr[u<<1].s+tr[u<<1|1].s;
	}
	void change(int u,int fir,int tol){
		tr[u].s+=(2*fir+(tr[u].r-tr[u].l)*tol)*(tr[u].r-tr[u].l+1)/2;
		tr[u].fir+=fir;
		tr[u].tol+=tol;
	}
	void pushdown(int u){
		change(u<<1,tr[u].fir,tr[u].tol);
		change(u<<1|1,tr[u].fir+(tr[u<<1|1].l-tr[u<<1].l)*tr[u].tol,tr[u].tol);
		tr[u].fir=tr[u].tol=0;
	}
	void build(int u,int l,int r){
		tr[u]={l,r};
		if(l==r) return;
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);	
	}
	void modify(int u,int l,int r,int fir,int tol){
		if(tr[u].l>=l&&tr[u].r<=r){
			change(u,fir+(tr[u].l-l)*tol,tol);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify(u<<1,l,r,fir,tol);
		if(r>mid) modify(u<<1|1,l,r,fir,tol);
		pushup(u);
	}
	int query(int u,int l,int r){
		if(l>r) return 0;
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].s;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,s=0;
		if(l<=mid) s+=query(u<<1,l,r);
		if(r>mid) s+=query(u<<1|1,l,r);
		return s;
	}
}tree;

signed main(){
	cin >> n >> T;
	for(int i=0; i<=n-1; i++) q[i].push_back(0);
	for(int i=1; i<=n; i++){
		int x;
		cin >> x;
		q[x].push_back(i);
	}
	for(int i=0; i<=n-1; i++) q[i].push_back(n+1);
	tree.build(1,-n,n);
	int ans=0;
	for(int i=0; i<=n-1; i++){
		int now=-1;
		for(int j=0; j<q[i].size()-1; j++){
			int l=q[i][j],r=q[i][j+1]-1,x=now+1,y=x-r+l;
			swap(x,y);
			ans+=tree.query(1,x-1,y-1);
			tree.modify(1,x,y,1,1);
			tree.modify(1,y+1,3*n,y-x+1,0);
			swap(x,y),now=y;
		}
		now=-1;
		for(int j=0; j<q[i].size()-1; j++){
			int l=q[i][j],r=q[i][j+1]-1,x=now+1,y=x-r+l;
			swap(x,y);
			tree.modify(1,x,y,-1,-1);
			tree.modify(1,y+1,3*n,-(y-x+1),0);
			swap(x,y),now=y;
		}
	}
	cout << ans << endl;
	return 0;
} 

\(2\)\(P8600\)
https://www.luogu.com.cn/problem/P8600
题意:给定一个排列\(n\),求满足如下条件的区间个数:将区间内元素排序,相邻两元素之间差值均为\(1\)
题解:对区间右端点进行扫描线,当扫到\(r\)时,动态维护\(1\sim r\)的点,每个点存储\(i\sim r\)这段后缀的信息。
一个区间满足要求的充要条件是,\(v=max\{a_i\}-min\{a_i\}-len=0\)
于是每个点要维护对应后缀的\(max,min,v\)
当右端点为\(r\)时,满足条件的左端点,即为\(1\sim r\)\(v=0\)的点,于是需要维护区间\(v\)的最小值和最小值个数。
后缀\(max,min,len\)都可以用单调栈配合区间加减维护。

#include <bits/stdc++.h>
#define int long long

using namespace std;

typedef long long LL;
const int N=5e4+10,INF=1e9;

int n;
int a[N];
int stk_mi[N],top_mi;
int stk_mx[N],top_mx;

struct Node{
	int l,r;
	int mi,cnt,add;
};

struct SegmentTree{
	Node tr[4*N];
	Node pushup(Node tl,Node tr){
		Node t;
		t.l=tl.l,t.r=tr.r;
		t.mi=min(tl.mi,tr.mi);
		t.cnt=0;
		if(tl.mi==t.mi) t.cnt+=tl.cnt;
		if(tr.mi==t.mi) t.cnt+=tr.cnt;
		t.add=0;
		return t;
	}
	void change(int u,int add){
		tr[u].mi+=add;
		tr[u].add+=add;
	}
	void pushdown(int u){
		change(u<<1,tr[u].add);
		change(u<<1|1,tr[u].add);
		tr[u].add=0;
	}
	void build(int u,int l,int r){
		tr[u]={l,r,INF,r-l+1,0};
		if(l==r) return;
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
	}
	void modify(int u,int l,int r,int add){
		if(tr[u].l>=l&&tr[u].r<=r){
			change(u,add);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify(u<<1,l,r,add);
		if(r>mid) modify(u<<1|1,l,r,add);
		tr[u]=pushup(tr[u<<1],tr[u<<1|1]);
	}
	Node query(int u,int l,int r){
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u];
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(r<=mid) return query(u<<1,l,r);
		else if(l>mid) return query(u<<1|1,l,r);
		else return pushup(query(u<<1,l,r),query(u<<1|1,l,r));
	}
}tree;

signed main(){
	cin >> n;
	for(int i=1; i<=n; i++) cin >> a[i];
	tree.build(1,1,n);
	int ans=0;
	for(int i=1; i<=n; i++){
		tree.modify(1,i,i,-INF);
		while(top_mi&&a[stk_mi[top_mi]]>=a[i]){
			tree.modify(1,stk_mi[top_mi-1]+1,stk_mi[top_mi],a[stk_mi[top_mi]]-a[i]);
			top_mi--;
		}
		stk_mi[++top_mi]=i;
		while(top_mx&&a[stk_mx[top_mx]]<=a[i]){
			tree.modify(1,stk_mx[top_mx-1]+1,stk_mx[top_mx],a[i]-a[stk_mx[top_mx]]);
			top_mx--;
		}
		stk_mx[++top_mx]=i;
		ans+=tree.query(1,1,i).cnt;
		tree.modify(1,1,i,-1);
	}
	cout << ans << endl;
	return 0;
}

\(3\)\(P8747\)
https://www.luogu.com.cn/problem/P8747
题意:给定序列\(\{a_i=i\}\)。维护如下操作:
\(1\):给定\(x\),将\(a_1\sim a_x\)降序排序。
\(2\):给定\(x\),将\(a_x\sim a_n\)升序排序。
题解:考虑在任意时刻,序列一定由前缀降序,和后缀升序,两个序列构成。
而在同一序列中的两个元素,大小关系是容易维护的,于是只需着重考虑记录两序列的构成。
不妨维护值域,令\(c_i\)为权值\(i\)当前处于降序序列还是升序序列中,初始全在升序序列中。
考虑设当前有\(x_1\)个处于降序,\(y_1\)个处于升序。操作完后,有\(x_2\)个处于降序,\(y_2\)个处于升序。
\(x_1<x_2\),说明有若干个升序序列中元素,进入降序序列中,而这些元素,一定是升序序列中靠近中心的那些元素。
而由于有序性,靠近中心的那些元素,恰好是对于序列中权值最小的若干元素。
于是直接在值域上二分找到最后一个需要修改元素,进行区间覆盖即可,用线段树易于维护。

#include <bits/stdc++.h>

using namespace std;

const int N=1e5+10;

struct SegmentTree{
	struct Node{
		int l,r;
		int s0,s1;
		int flag;
	}tr[4*N]; 
	void pushup(int u){
		tr[u].s0=tr[u<<1].s0+tr[u<<1|1].s0;
		tr[u].s1=tr[u<<1].s1+tr[u<<1|1].s1;
	}
	void change(int u,int flag){
		if(flag==-1) return;
		if(flag) tr[u].s0=0,tr[u].s1=tr[u].r-tr[u].l+1;
		else tr[u].s0=tr[u].r-tr[u].l+1,tr[u].s1=0;
		tr[u].flag=flag;
	}
	void pushdown(int u){
		change(u<<1,tr[u].flag);
		change(u<<1|1,tr[u].flag);
		tr[u].flag=-1;
	}
	void build(int u,int l,int r){
		tr[u]={l,r,0,r-l+1,-1};
		if(l==r) return;
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
	}
	void modify(int u,int l,int r,int flag){
		if(tr[u].l>=l&&tr[u].r<=r){
			change(u,flag);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify(u<<1,l,r,flag);
		if(r>mid) modify(u<<1|1,l,r,flag);
		pushup(u);
	}
	void solve_down(int u,int s1){
		if(tr[u].l==tr[u].r){
			modify(1,1,tr[u].l,0);
			return;
		}
		pushdown(u);
		if(tr[u<<1].s1>=s1) solve_down(u<<1,s1);
		else solve_down(u<<1|1,s1-tr[u<<1].s1);
		//pushup(u);
	}
	void solve_up(int u,int s0){
		if(tr[u].l==tr[u].r){
			modify(1,1,tr[u].l,1);
			//cout << tr[1].s0 << " " << tr[1].s1 << " OK" << endl;
			//cout << tr[u].l << " " << tr[u].s0 << " " << tr[u].s1 << " OK" << endl;
			return;
		}
		pushdown(u);
		if(tr[u<<1].s0>=s0) solve_up(u<<1,s0);
		else solve_up(u<<1|1,s0-tr[u<<1].s0);
		//pushup(u);
	}
	void print_down(int u){
		if(tr[u].l==tr[u].r){
			cout << tr[u].l << " ";
			return;
		}
		pushdown(u);
		if(tr[u<<1|1].s0) print_down(u<<1|1);
		if(tr[u<<1].s0) print_down(u<<1);
	}
	void print_up(int u){
		if(tr[u].l==tr[u].r){
			cout << tr[u].l << " ";
			return;
		}
		pushdown(u);
		//cout << u << " " << tr[u<<1].s1 << " " << tr[u<<1|1].s1 << " NO" << endl;
		if(tr[u<<1].s1) print_up(u<<1);
		if(tr[u<<1|1].s1) print_up(u<<1|1);
	}
}tree;

int main(){
	int n,m;
	cin >> n >> m;
	tree.build(1,1,n);
	int x=0;
	while(m--){
		int tp,p;
		cin >> tp >> p;
		if(!tp&&p>x){
			tree.solve_down(1,p-x);
			x=p;
		}
		if(tp&&p-1<x){
			tree.solve_up(1,x-p+1);
			x=p-1;
		}
		//tree.print_down(1);
		//tree.print_up(1);
		//cout << endl;
	}
	tree.print_down(1);
	tree.print_up(1);
	cout << endl;
	return 0;
}

\(4\)\(Sensors\)
题意:一个序列上\(n\)个点,初始全为\(0\),同时有\(m\)个区间。
每次操作将一个点由\(1\)变为\(0\),问每次操作后,有多少个区间只有一个\(1\)
题解:将一个大区间分为若干小区间,大区间只有一个\(1\),当且仅当所有小区间\(1\)的个数均小于等于\(1\)
直接用线段树维护序列,天然的将每个询问区间,划分为\(O(logn)\)个树上节点。
当树上节点代表小区间\(1\)的个数更新为\(0/1\)时候,更新包含该小区间的所有询问区间的信息。

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N=5e5+10;

int n,m;
int l[N],r[N],len[N];
LL ans;

struct SegmentTree{
	struct Node{
		int l,r,s;
		vector<int> q;
	}tr[4*N];
	void build(int u,int l,int r){
		tr[u]={l,r,r-l+1};
		tr[u].q.clear();
		if(l==r) return;
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
	}
	void change(int u,int l,int r,int x){
		if(tr[u].l>=l&&tr[u].r<=r){
			tr[u].q.push_back(x);
			return;
		}
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) change(u<<1,l,r,x);
		if(r>mid) change(u<<1|1,l,r,x);
	}
	void modify(int u,int k){
		tr[u].s--;
		if(tr[u].s<=1)
			for(int i=0; i<tr[u].q.size(); i++){
				int x=tr[u].q[i];
				if(tr[u].s==0) len[x]--;
				else len[x]-=tr[u].r-tr[u].l;
				if(!len[x]) ans-=(LL)x*x;
				else if(len[x]==1) ans+=(LL)x*x;
			}
		if(tr[u].l==tr[u].r) return;
		int mid=tr[u].l+tr[u].r>>1;
		if(k<=mid) modify(u<<1,k);
		else modify(u<<1|1,k);
	}
}tree;

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0); 
	int T;
	cin >> T;
	while(T--){
		cin >> n >> m;
		tree.build(1,1,n);
		ans=0;
		for(int i=1; i<=m; i++){
			cin >> l[i] >> r[i];
			l[i]++,r[i]++,len[i]=r[i]-l[i]+1;
			tree.change(1,l[i],r[i],i);
			if(len[i]==1) ans+=(LL)i*i;
		}
		cout << ans << " ";
		for(int i=1; i<=n; i++){
			int x;
			cin >> x;
			x=(x+ans)%n+1;
			tree.modify(1,x);
			cout << ans << " ";
		}
		cout << endl;
	}
	return 0;
}

\(5\)\(Intersection\ of\ Paths\)
题意:给定一棵\(n\)个节点的树,多次询问。
每次询问修改一条边的边权,并且给出\(k\),要求在树上选取\(k\)条端点两两不同的路径,最大化被所有路径包含的边权之和。
题解:考虑若一条边能被\(k\)条路径覆盖,等价于将其删去后,所剩两个连通块大小均\(\geq k\)
所以当\(k\)固定时,备选边集合构成了一棵树,而大值就是这棵树的直径。
\(k+1\)对应边集一定包含\(k\)对应边集,所以询问按\(k\)从小到大排序,依次处理。
删边可看成将边权变为\(0\),于是问题转化为,给定一棵树,动态修改边权大小,回答树的直径。
这是经典的动态树直径问题,下给出线段树在欧拉序上维护的方法。
考虑\(dist(x,y)=dep(x)+dep(y)-2*dep(lca(x,y))\)
当边权非负数时,欧拉序上有性质,一段区间\([x,y]\)\(x,y\)对应的\(lca\)\(dep\),是\([x,y]\)\(dep\)最小值。
所以路径可以在欧拉序上表示为\(d(x)+d(y)-2*min\{dep(k)\}\)\(x\leq k\leq y\),用线段树维护。
对于修改边权,造成的结果是对子树\(dep\)的区间加,容易维护。

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N=1e6+10;

int n,m;
int sz[N],dep[N],ans[N];
int in[N],out[N],dfn_v[N],num;
int h[N],e[N],w[N],ne[N],idx;

struct Edge{
	int k,x,y,z;
	bool operator <(Edge t){
		return k<t.k;
	}
}E[N],T[N];

struct Query{
	int a,b,k,id;
	bool operator <(Query t){
		return k<t.k;
	}
}q[N];

struct SegmentTree{
	struct Node{
		int l,r;
		int mi,mx;
		int Lx,xR,LxR;
		int add;
	}tr[4*N];
	Node pushup(Node tl,Node tr){
		Node t;
		t.l=tl.l,t.r=tr.r;
		t.mi=min(tl.mi,tr.mi);
		t.mx=max(tl.mx,tr.mx);
		t.Lx=max(tl.Lx,tr.Lx);
		t.Lx=max(t.Lx,tl.mx-2*tr.mi);
		t.xR=max(tl.xR,tr.xR);
		t.xR=max(t.xR,-2*tl.mi+tr.mx);
		t.LxR=max(tl.LxR,tr.LxR);
		t.LxR=max(t.LxR,tl.mx+tr.xR);
		t.LxR=max(t.LxR,tl.Lx+tr.mx);
		t.add=0;
		return t;
	}
	void change(int u,int add){
		tr[u].mi+=add,tr[u].mx+=add;
		tr[u].Lx-=add,tr[u].xR-=add;
		tr[u].add+=add;
	}
	void pushdown(int u){
		change(u<<1,tr[u].add);
		change(u<<1|1,tr[u].add);
		tr[u].add=0;
	}
	void build(int u,int l,int r){
		tr[u]={l,r};
		if(l==r){
			int id=dfn_v[l];
			tr[u].mi=tr[u].mx=dep[id];
			tr[u].Lx=tr[u].xR=-dep[id];
			tr[u].LxR=tr[u].add=0;
			return;
		}
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
		tr[u]=pushup(tr[u<<1],tr[u<<1|1]);
	}
	void modify(int u,int l,int r,int add){
		if(tr[u].l>=l&&tr[u].r<=r){
			change(u,add);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify(u<<1,l,r,add);
		if(r>mid) modify(u<<1|1,l,r,add);
		tr[u]=pushup(tr[u<<1],tr[u<<1|1]);
	}
}tree;

void add(int a,int b,int c){
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u,int fa){
	sz[u]=1,in[u]=++num,dfn_v[num]=u;
	for(int i=h[u]; i!=-1; i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dep[j]=dep[u]+w[i];
		dfs(j,u);
		sz[u]+=sz[j];
		dfn_v[++num]=u;
	}
	out[u]=num;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	memset(h,-1,sizeof h);
	cin >> n >> m;
	for(int i=1; i<=n-1; i++){
		int a,b,c;
		cin >> a >> b >> c;
		add(a,b,c),add(b,a,c);
		E[i]={0,a,b,c};
		//cout << E[i].x << endl;
	}
	//cout << E[1].x << endl;
	dfs(1,-1);
	//cout << E[1].x << endl;
	tree.build(1,1,num);
	for(int i=1; i<=n-1; i++){
		int a=E[i].x,b=E[i].y,c=E[i].z;
		if(sz[a]>sz[b]) swap(a,b);
		E[i]={min(sz[a],n-sz[a]),a,b,c};
	}
	//for(int i=1; i<=n-1; i++) cout << E[i].x << " ";
	//cout << endl;
	memcpy(T,E,sizeof E);
	sort(E+1,E+n);
	//reverse(E+1,E+n);
	for(int i=1; i<=m; i++){
		int a,b,k;
		cin >> a >> b >> k;
		q[i]={a,b,k,i};
	}
	sort(q+1,q+m+1);
	//reverse(q+1,q+m+1);
	//cout << tree.tr[1].LxR << endl;	
	for(int i=1,j=1; i<=m; i++){
		while(j<=n-1&&E[j].k<q[i].k){
			int k=E[j].x,x=-E[j].z;
			tree.modify(1,in[k],out[k],x);
			j++;
		}
		//cout << tree.tr[1].LxR << endl;
		int a=q[i].a,y=q[i].b;
		int k=T[a].x,x=T[a].z;
		if(T[a].k>=q[i].k) tree.modify(1,in[k],out[k],y-x);
		//cout << q[i].id << " OK" << endl;
		ans[q[i].id]=tree.tr[1].LxR;
		if(T[a].k>=q[i].k) tree.modify(1,in[k],out[k],x-y);
	}
	for(int i=1; i<=m; i++) cout << ans[i] << endl;
	return 0;
}

\(6\)\(CF1954E\)
https://www.luogu.com.cn/problem/CF1954E
题意:\(n\)个怪物站一排,第\(i\)个怪物血量为\(a_i\)。当你攻击力为\(k\)时,你可以选择一段极长的连续的存活的怪物,使他们的血量都减少\(k\),对每个\(k\)回答杀死所有怪物所需的回合数。
题解:对每个\(k\)直接模拟每回合,这样总回合数为\(O(\sum \frac{n}{i})=O(nlogn)\),我们只需快速的计算出每个回合的极大连续段个数。
发现即使\(k\)不同,但是当\(tk\)相同,\(t\)为已经完成的回合数,即总伤害相同时,局面是相同的。
于是只需对所有\(x\),求出造成伤害\(x\)时,当前的极大连续段个数即可。
发现当\(x\geq a_i\)时,怪物死亡,当\(x<a_i\)时,怪物存活。
于是只需按\(a_i\)排序,对\(x\)进行扫描线统计即可。

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N=2e5+10;

int n;
int f[N];
bool st[N];
struct Node{
	int x,pos;
	bool operator <(Node t){
		return x<t.x;
	}
}q[N];

int main(){
	cin >> n;
	for(int i=1; i<=n; i++){
		int x;
		cin >> x;
		q[i]={x,i};
	}
	sort(q+1,q+n+1);
	for(int i=1; i<=n; i++) st[i]=1;
	int res=1;
	for(int i=0,j=1; i<=q[n].x; i++){
		while(j<=n&&q[j].x<=i){
			int pos=q[j].pos;
			if(st[pos-1]&&st[pos+1]) res++;
			else if(!st[pos-1]&&!st[pos+1]) res--;
			st[pos]=0;
			j++;
		}
		f[i]=res;
	}
	for(int i=1; i<=q[n].x; i++){
		LL res=0,ans=0;
		while(1){
			ans+=f[res];
			res+=i;
			if(f[res]<=0) break;
		}
		cout << ans << " ";
	}
	cout << endl;
	return 0;
}

\(7\):最近公共祖先
https://codeforces.com/gym/105851
题意:给定两棵树\(S,T\),求有多少点对\(x,y\),在两棵树上\(lca\)相同。
题解:在\(S\)上枚举\(lca\),设当前\(lca\)\(x\),按套路合并各子树集合。
设当前待合并集合为\(L,R\)\(|L|>|R|\),枚举\(|R|\)中元素\(r\),而满足条件的\(l\in L\),即为:
\(T\)里在\(x\)的子树中,且不在\(x\)的儿子中包含\(r\)的那个子树。
显然建立点和\(T\)\(dfn\)序的映射,可以用线段树简单维护了。

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N=1e6+10;

int n;
int dfn[N],num;
int szS[N],son[N];
int szT[N],dep[N],f[N][22];
int hS[N],hT[N],e[N],ne[N],idx;
LL ans;

struct Node{
	int l,r,s;
}tr[N];

void build(int u,int l,int r){
	tr[u]={l,r};
	if(l==r) return;
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
}

void modify(int u,int k,int x){
	if(tr[u].l==tr[u].r){
		tr[u].s+=x;
		return;
	}
	int mid=tr[u].l+tr[u].r>>1;
	if(k<=mid) modify(u<<1,k,x);
	else modify(u<<1|1,k,x);
	tr[u].s=tr[u<<1].s+tr[u<<1|1].s;
}

int query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r) return tr[u].s;
	int mid=tr[u].l+tr[u].r>>1,s=0;
	if(l<=mid) s+=query(u<<1,l,r);
	if(r>mid) s+=query(u<<1|1,l,r);
	return s;
}

void add(int a,int b,int h[]){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs_S(int u,int fa){
	szS[u]=1,son[u]=0;
	for(int i=hS[u]; i!=-1; i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfs_S(j,u);
		if(szS[j]>szS[son[u]]) son[u]=j;
		szS[u]+=szS[j];
	}
}

void dfs_T(int u,int depth){
	//cout << u << " " << f[u][0] << endl;
	szT[u]=1,dep[u]=depth,dfn[u]=++num;
	for(int i=hT[u]; i!=-1; i=ne[i]){
		int j=e[i];
		//cout << u << " " << j << " " << depth << " " << f[u][0] << endl;
		if(j==f[u][0]) continue;
		f[j][0]=u;
		for(int k=1; k<=18; k++)
			f[j][k]=f[f[j][k-1]][k-1];
		dfs_T(j,depth+1);
		szT[u]+=szT[j];
	}
}

void find_S(int u,int fa,vector<int> &q){
	q.push_back(u);
	for(int i=hS[u]; i!=-1; i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		find_S(j,u,q);
	}
}

void calc(int x,int r){
	if(dfn[r]>=dfn[x]&&dfn[r]<=dfn[x]+szT[x]-1){
		int now=r;
		for(int i=18; i>=0; i--)
			if(dep[f[now][i]]>dep[x]) now=f[now][i];
		ans+=query(1,dfn[x],dfn[x]+szT[x]-1);
		if(r!=x) ans-=query(1,dfn[now],dfn[now]+szT[now]-1);
	}
}

void work_S(int u,int fa,int ret){
	for(int i=hS[u]; i!=-1; i=ne[i]){
		int j=e[i];
		if(j==fa||j==son[u]) continue;
		work_S(j,u,0);
	}
	if(son[u]) work_S(son[u],u,1);
	calc(u,u);
	modify(1,dfn[u],1);
	for(int i=hS[u]; i!=-1; i=ne[i]){
		int j=e[i];
		if(j==fa||j==son[u]) continue;
		vector<int> q;
		find_S(j,u,q);
		for(int k=0; k<q.size(); k++) calc(u,q[k]);
		for(int k=0; k<q.size(); k++) modify(1,dfn[q[k]],1);
	}
	if(!ret){
		vector<int> q;
		find_S(u,fa,q);
		for(int k=0; k<q.size(); k++) modify(1,dfn[q[k]],-1);
	}
}

int main(){
	int T;
	cin >> T;
	while(T--){
		cin >> n;
		idx=num=0;
		for(int i=1; i<=n; i++) hS[i]=-1;
		for(int i=1; i<=n; i++) hT[i]=-1;
		for(int i=1; i<=n-1; i++){
			int a,b;
			cin >> a >> b;
			add(a,b,hS),add(b,a,hS);
		}
		for(int i=1; i<=n-1; i++){
			int a,b;
			cin >> a >> b;
			add(a,b,hT),add(b,a,hT);
		}
		dfs_T(1,1);
		dfs_S(1,-1);
		build(1,1,n);
		ans=0;
		work_S(1,-1,0);
		cout << ans << endl;
	}
	return 0;
}

\(8\)\(CF2018D\)
https://www.luogu.com.cn/problem/CF2018D
题意:给定一个序列,每个点有权值\(a_i\),要求从序列中选若干不相邻的数,一个方案的价值是所选数的最大权值,加最小权值,加个数,求最大价值。
题解:发现至少要选一个最大值,于是枚举最小值,对于一个最小值,有若干可选连续段。
每个长\(x\)的可选连续段,最多选\(\lceil \frac{x}{2} \rceil\)个数。对每个连续段在选取个数取最优的条件下,尽可能选到最大值。
如果最后所有连续段都没有选到最大值,一定可以少选取一个数,使最大值被选到,于是答案减一。
具体来说,偶数长度连续段一定可以选到最大值,对于奇数长度连续段,记录其奇数位置上是否存在最大值即可。

#include <bits/stdc++.h>

using namespace std;

typedef pair<int,int> PII;
const int N=2e5+10;

int n,res,cnt;
int a[N];
int p[N],len[N];
bool st[N],st0[N],st1[N];
PII q[N];

int find(int x){
	if(x==p[x]) return x;
	return p[x]=find(p[x]);
}

void merge(int x,int y){
	x=find(x),y=find(y);
	res-=(len[x]+1)/2;
	res-=(len[y]+1)/2;
	if(len[x]%2&&st1[x]) cnt--;
	if((len[x]%2==0)&&(st0[x]||st1[x])) cnt--;
	if(len[y]%2&&st1[y]) cnt--;
	if((len[y]%2==0)&&(st0[y]||st1[y])) cnt--;
	if(len[x]%2) st0[x]|=st1[y],st1[x]|=st0[y];
	else st0[x]|=st0[y],st1[x]|=st1[y];
	p[y]=x,len[x]+=len[y];
	res+=(len[x]+1)/2;
	if(len[x]%2&&st1[x]) cnt++;
	if((len[x]%2==0)&&(st0[x]||st1[x])) cnt++;
}

int main(){
	int T;
	cin >> T;
	while(T--){
		cin >> n;
		for(int i=1; i<=n; i++){
			int x;
			cin >> x;
			q[i]={x,i};
		}
		sort(q+1,q+n+1);
		reverse(q+1,q+n+1);
		for(int i=1; i<=n; i++) p[i]=i,len[i]=1;
		for(int i=1; i<=n; i++) st[i]=st0[i]=st1[i]=0;
		for(int i=1; i<=n; i++)
			if(q[i].first==q[1].first) st1[q[i].second]=1;
		int ans=2*q[1].first+1;
		res=cnt=0;
		for(int i=1; i<=n; i++){
			int t=q[i].second;
			res++,st[t]=1;
			if(st1[t]) cnt++;
			if(t>1&&st[t-1]) merge(t-1,t);
			if(t<n&&st[t+1]) merge(t,t+1);
			ans=max(ans,q[1].first+q[i].first+res-1+(cnt>0));
		}
		cout << ans << endl;
	}
	return 0;
}

\(9\)\(CF1985H2\)
https://www.luogu.com.cn/problem/CF1985H2
题意:一个\(n*m\)\(01\)方阵,可以将一行和一列同时染成\(1\),使最大\(1\)连通块尽可能大。
题解:考虑枚举染色的行和列,考虑计算当前行列所在连通块大小。
第一部分贡献是当前行列中原本的\(0\)个数。
第二部分贡献是那些和当前行列有交集的连通块,考虑预处理。
先处理出每个连通块的上下左右四个方向的极值,设上下极值为\(r_1,r_2\),左右极值为\(c_1,c_2\)
显然当枚举的行在\([r_1-1,r_2+1]\)时,可以得到该连通块的贡献。
当枚举的列在\([c_1-1,c_2+1]\)时,也可以得到该连通块的贡献。
对行列分别开一个差分数组,进行计算即可。
但当行列都满足要求的时候,贡献会被计算两次,减掉重复计算的贡献。
于是再开一个二维差分数组,进行计算即可。

#include <bits/stdc++.h>

using namespace std;

const int N=1e6+10;

int n,m;
int p[N],sz[N];
int r1[N],r2[N],c1[N],c2[N];
int sr[N],sc[N];
int sumr[N],sumc[N];
vector<int> s[N];
vector<char> g[N];
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};

int get(int x,int y){
	return (x-1)*m+y;
}

int find(int x){
	if(x==p[x]) return x;
	return p[x]=find(p[x]);
}

void merge(int x,int y){
	x=find(x),y=find(y);
	if(x==y) return;
	p[x]=y,sz[y]+=sz[x];
	r1[y]=min(r1[y],r1[x]);
	r2[y]=max(r2[y],r2[x]);
	c1[y]=min(c1[y],c1[x]);
	c2[y]=max(c2[y],c2[x]);
}

int main(){
	int T;
	cin >> T;
	while(T--){
		cin >> n >> m;
		for(int i=1; i<=n; i++){
			g[i].clear();
			g[i].push_back(0);
			for(int j=1; j<=m; j++){
				char ch;
				cin >> ch;
				g[i].push_back(ch);	
			}
		}
		for(int i=1; i<=n; i++)
			for(int j=1; j<=m; j++){
				p[get(i,j)]=get(i,j);
				sz[get(i,j)]=1;
				r1[get(i,j)]=r2[get(i,j)]=i;
				c1[get(i,j)]=c2[get(i,j)]=j;
			}
		for(int i=1; i<=n; i++)
			for(int j=1; j<=m; j++)	
				for(int k=0; k<4; k++){
					int x=i+dx[k],y=j+dy[k];
					if(x<1||x>n||y<1||y>m) continue;
					if(g[i][j]!='#'||g[x][y]!='#') continue;
					merge(get(i,j),get(x,y));
				}
		for(int i=0; i<=n; i++) sr[i]=0;
		for(int i=0; i<=m; i++) sc[i]=0;
		for(int i=0; i<=n+2; i++){
			s[i].clear();
			for(int j=0; j<=m+2; j++) 
				s[i].push_back(0);
		}
		int ans=0;
		for(int i=1; i<=n; i++)
			for(int j=1; j<=m; j++)
				if(p[get(i,j)]==get(i,j)&&g[i][j]=='#'){
					ans=max(ans,sz[get(i,j)]);
					sr[r1[get(i,j)]-1]+=sz[get(i,j)];
					sr[r2[get(i,j)]+2]-=sz[get(i,j)];
					sc[c1[get(i,j)]-1]+=sz[get(i,j)];
					sc[c2[get(i,j)]+2]-=sz[get(i,j)];
					s[r1[get(i,j)]-1][c1[get(i,j)]-1]+=sz[get(i,j)];
					s[r1[get(i,j)]-1][c2[get(i,j)]+2]-=sz[get(i,j)];
					s[r2[get(i,j)]+2][c1[get(i,j)]-1]-=sz[get(i,j)];
					s[r2[get(i,j)]+2][c2[get(i,j)]+2]+=sz[get(i,j)];
				}
		for(int i=1; i<=n; i++) sr[i]+=sr[i-1];
		for(int i=1; i<=m; i++) sc[i]+=sc[i-1];
		for(int i=0; i<=n; i++)
			for(int j=0; j<=m; j++){
				if(i) s[i][j]+=s[i-1][j];
				if(j) s[i][j]+=s[i][j-1];
				if(i&&j) s[i][j]-=s[i-1][j-1];
			}
		for(int i=1; i<=n; i++) sumr[i]=0;
		for(int i=1; i<=m; i++) sumc[i]=0;
		for(int i=1; i<=n; i++)
			for(int j=1; j<=m; j++)
				if(g[i][j]=='.') sumr[i]++,sumc[j]++;
		for(int i=1; i<=n; i++)
			for(int j=1; j<=m; j++)
				ans=max(ans,sr[i]+sc[j]-s[i][j]+
				sumr[i]+sumc[j]-(g[i][j]=='.'));
		cout << ans << endl;
	}
	return 0;
}

\(10\)\(LCA\ AND\ MST\)
https://codeforces.com/gym/105887
题意:给定一棵树\(T\),每个点有权值,再给定一张完全图\(G\)\((u,v)\)的边权为\(T\)\(lca(u,v)\)的点权,有若干次子树加操作,每次操作后回答\(G\)\(MST\)
题解:完全图最小生成树问题考虑\(B\)算法,即每个点选择最小边权。
在固定点\(u\)时候,将点按\(lca(u,v)\)为代表元分类,每类点对应权值相同。
于是可看场每个点向其祖先中权值最小点连边,而这样连边后,一定构成一颗生成树。
于是令\(f(i)\)\(i\)祖先中的最小权值,则答案为\(\sum f(i)\)
而对于\(p\)的子树整体加\(x\),对于\(p\)子树内一节点\(u\),若\(f(u)\)对应的节点在\(u\)\(p\)路径上,则\(f'(u)=min\{f(p),f(u)+x\}\)
\(f(u)\)对应节点不在\(u\)\(p\)路径上,则\(f'(u)=f(p)\),且\(f(p)=f(u)\leq f(u)+x\),则\(f'(u)=f(p)=min\{f(p),f(u)+x\}\)
于是对于\(p\)子树加,总是将\(p\)子树内所有节点\(u\),进行\(f(u)=min\{f(p),f(u)+x\}\)的操作。
可以将操作拆分成,先区间加\(x\),再区间和\(f(p)\)\(min\),最终要完成的即为区间求和,这些可以用\(Segment\ Tree\ Beats\)完成。
对于区间最值操作相关有问题的,可见https://www.cnblogs.com/lastxuans/articles/18764597

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N=2e5+10,INF=1e14;

int n,m;
int a[N],f[N],sz[N];
int dfn_id[N],id_dfn[N],num;
int h[N],e[2*N],ne[2*N],idx;

struct SegmentTree{
	struct Node{
		int l,r,s;
		int mx1,mx2,cnt;
		int add,cov;
	}tr[4*N];
	void pushup(int u){
		tr[u].s=tr[u<<1].s+tr[u<<1|1].s;
		tr[u].mx1=max(tr[u<<1].mx1,tr[u<<1|1].mx1);
		tr[u].cnt=0;
		if(tr[u<<1].mx1==tr[u].mx1) tr[u].cnt+=tr[u<<1].cnt;
		if(tr[u<<1|1].mx1==tr[u].mx1) tr[u].cnt+=tr[u<<1|1].cnt;
		tr[u].mx2=-INF;
		if(tr[u<<1].mx1!=tr[u].mx1&&tr[u<<1].mx1>tr[u].mx2) tr[u].mx2=tr[u<<1].mx1;
		if(tr[u<<1].mx2!=tr[u].mx1&&tr[u<<1].mx2>tr[u].mx2) tr[u].mx2=tr[u<<1].mx2;
		if(tr[u<<1|1].mx1!=tr[u].mx1&&tr[u<<1|1].mx1>tr[u].mx2) tr[u].mx2=tr[u<<1|1].mx1;
		if(tr[u<<1|1].mx2!=tr[u].mx1&&tr[u<<1|1].mx2>tr[u].mx2) tr[u].mx2=tr[u<<1|1].mx2;
	}
	void change_add(int u,int add){
		tr[u].s+=add*(tr[u].r-tr[u].l+1);
		tr[u].mx1+=add;
		if(tr[u].mx2!=-INF) tr[u].mx2+=add;
		tr[u].add+=add;
		if(tr[u].cov!=INF) tr[u].cov+=add;
	}
	void change_cov(int u,int cov){
		if(tr[u].mx1<=cov) return;
		tr[u].s-=(tr[u].mx1-cov)*tr[u].cnt;
		tr[u].mx1=tr[u].cov=cov;
	}
	void pushdown(int u){
		if(tr[u].add){
			change_add(u<<1,tr[u].add);
			change_add(u<<1|1,tr[u].add);
			tr[u].add=0;
		}
		if(tr[u].cov!=INF){
			change_cov(u<<1,tr[u].cov);
			change_cov(u<<1|1,tr[u].cov);
			tr[u].cov=INF;
		}
	}
	void build(int u,int l,int r){
		tr[u]={l,r,0,0,-INF,1,0,INF};
		if(l==r){
			int id=dfn_id[l];
			tr[u].s=tr[u].mx1=f[id];
			return;
		}
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
		pushup(u);
	}
	void modify_add(int u,int l,int r,int add){
		if(tr[u].l>=l&&tr[u].r<=r){
			change_add(u,add);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify_add(u<<1,l,r,add);
		if(r>mid) modify_add(u<<1|1,l,r,add);
		pushup(u);
	}
	void modify_cov(int u,int l,int r,int cov){
		if(tr[u].mx1<=cov) return;
		if(tr[u].l>=l&&tr[u].r<=r&&tr[u].mx2<cov){
			change_cov(u,cov);
			return;
		}
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1;
		if(l<=mid) modify_cov(u<<1,l,r,cov);
		if(r>mid) modify_cov(u<<1|1,l,r,cov);
		pushup(u);
	}
	int query(int u,int l,int r){
		//cout << u << " " << tr[u].l << " " << tr[u].r << " " << tr[u].s << endl;
		if(tr[u].l>=l&&tr[u].r<=r) return tr[u].s;
		pushdown(u);
		int mid=tr[u].l+tr[u].r>>1,s=0;
		if(l<=mid) s+=query(u<<1,l,r);
		if(r>mid) s+=query(u<<1|1,l,r);
		return s;
	}
}tree;

void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u,int fa){
	if(fa!=-1){
		f[u]=a[fa];
		if(fa!=1) f[u]=min(f[u],f[fa]);
	}
	sz[u]=1,id_dfn[u]=++num,dfn_id[num]=u;
	for(int i=h[u]; i!=-1; i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfs(j,u);
		sz[u]+=sz[j];
	}
}

signed main(){
	memset(h,-1,sizeof h);
	cin >> n >> m;
	for(int i=1; i<=n-1; i++){
		int a,b;
		cin >> a >> b;
		add(a,b),add(b,a);
	}
	for(int i=1; i<=n; i++) cin >> a[i];
	dfs(1,-1);
	tree.build(1,1,n);
	cout << tree.query(1,2,n) << endl;
	for(int i=1; i<=m; i++){
		int p,x;
		cin >> p >> x;
		int tmp=tree.query(1,id_dfn[p],id_dfn[p]);
		tree.modify_add(1,id_dfn[p],id_dfn[p]+sz[p]-1,x);
		if(p!=1) tree.modify_cov(1,id_dfn[p],id_dfn[p]+sz[p]-1,tmp);
		cout << tree.query(1,2,n) << endl;
	}
	return 0;
}
posted @ 2025-05-28 11:09  junliang123  阅读(41)  评论(0)    收藏  举报