线段树练习

一键挖矿
https://www.cnblogs.com/ctmlpfs/p/13858405.html

#include<bits/stdc++.h>
using namespace std;
#define N 2000010
#define int long long
#define f(x) (x==1||x==3)
int n,m,tg[N],x[N],y[N],tx[4]={1,-1,1,-1},ty[4]={1,1,-1,-1},ans;
vector<int>v[N];
struct va{
	int x,c;
}c[N];
va operator +(va x,va y){
	va z=(va){min(x.x,y.x),0};
	if(z.x==x.x)
		z.c+=x.c;
	if(z.x==y.x)
		z.c+=y.c;
	return z;
}
void pd(int o){
	if(tg[o]){
		tg[o*2]+=tg[o];
		tg[o*2+1]+=tg[o];
		c[o*2].x+=tg[o];
		c[o*2+1].x+=tg[o];
		tg[o]=0;
	}
}
void up(int o){
	c[o]=c[o*2]+c[o*2+1];
}
void ad(int o,int l,int r,int x,int y,int z){
	if(r<x||y<l)
		return;
	if(x<=l&&r<=y){
		tg[o]+=z;
		c[o].x+=z;
		return;
	}
	int md=(l+r)/2;
	pd(o);
	ad(o*2,l,md,x,y,z);
	ad(o*2+1,md+1,r,x,y,z);
	up(o);
}
va qc(int o,int l,int r,int x,int y){
	if(r<x||y<l)
		return (va){n*m+1,0};
	if(x<=l&&r<=y)
		return c[o];
	pd(o);
	int md=(l+r)/2;
	return qc(o*2,l,md,x,y)+qc(o*2+1,md+1,r,x,y);
}
void bd(int o,int l,int r){
	if(l==r){
		c[o].c=1;
		return;
	}
	int md=(l+r)/2;
	bd(o*2,l,md);
	bd(o*2+1,md+1,r);
	up(o);
}
void av(int a,int b,int c,int d){
	int e[5]={0,a,b,c,d};
	sort(e+1,e+5);
	int p=lower_bound(e+1,e+5,a)-e;
	for(int i=1;i<=p;i++){
		int v=f(p-i+1)-f(p-i);
		ad(1,1,n*m,e[i-1]+1,e[i],v);
	}
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=0;i<=n+1;i++)
		v[i].resize(m+5);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			scanf("%lld",&v[i][j]);
			x[v[i][j]]=i;
			y[v[i][j]]=j;
		}
	bd(1,1,n*m);
	for(int j=0;j<=m+1;j++)
		v[0][j]=v[n+1][j]=n*m+1;
	for(int i=0;i<=n+1;i++)
		v[i][0]=v[i][m+1]=n*m+1;
	for(int i=1;i<=n*m;i++){
		int a=x[i],b=y[i];
		for(int j=0;j<4;j++)
			av(v[a][b],v[a+tx[j]][b],v[a][b+ty[j]],v[a+tx[j]][b+ty[j]]);
		va tv=(va){4,0};
		tv=tv+qc(1,1,n*m,1,i);
		ans+=tv.c;
	}
	printf("%lld",ans);
}

斑斓之地
定义被蛇覆盖过的点为关键点,没有覆盖的为非关键点。
题目要算在某个矩形中非关键点连通块个数,可以使用平面图的euler定理(|V|-|E|+|F|=)连通块数。
计算(|V|)相当于让我们计算矩形([a,b],[l,r])的非关键点个数。
考虑容斥成((r-l+1)*(b-a+1)-)关键点个数。
这可以简单的使用可持久化线段树维护。
计算(|E|)可以计算横的边和竖的边,这同样也是个简单的二维数点问题。
问题转化为计算(|F|)
原图的连通区域由两种组成:
第一个是一个(1*1)的矩形。实际上我们要计算的就是包含(4)个非关键点的(2*2)矩形。
这也可以使用可持久化线段树维护。
但是这个算法还有错误。
可能所有关键点被这个矩形包含了。
我们在上面没有考虑。
设关键点的最小(x)坐标为(mnx),最大(x)坐标为(mxx)
最小(y)坐标为(mny),最大(x)坐标为(mxy)
(a<mnx)(l<mny)(mxx<b)(mxy<r)(|F|)要+1。
时间复杂度(O(nmlog_2nm))

#include<bits/stdc++.h>
using namespace std;
#define N 200010
#define int long long
char s[N];
int r,c,m,q,sr,sc,mxx,mnx,mxy,mny,a[N];
vector<int>v[4][N];
struct sgt{
	int lc[N*20],rc[N*20],ct,sz[N*20],rt[N];
	void ad(int &o,int p,int l,int r,int x){
		o=++ct;
		sz[o]=sz[p]+1;
		lc[o]=lc[p];
		rc[o]=rc[p];
		if(l==r)
			return;
		int md=(l+r)/2;
		if(x<=md)
			ad(lc[o],lc[p],l,md,x);
		else
			ad(rc[o],rc[p],md+1,r,x);
	}
	int qu(int o,int l,int r,int x,int y){
		if(r<x||y<l)
			return 0;
		if(x<=l&&r<=y)
			return sz[o];
		int md=(l+r)/2;
		return qu(lc[o],l,md,x,y)+qu(rc[o],md+1,r,x,y);
	}
	int qq(int l,int r,int a,int b){
		if(l>r||a>b)
			return 0;
		return qu(rt[r+1],0,c,a,b)-qu(rt[l],0,c,a,b);
	}
}sg[4];
void ad(int x,int y){
	x++;
	y++;
	v[0][x].push_back(y);
	v[1][x-1].push_back(y);
	v[1][x].push_back(y);
	v[2][x].push_back(y);
	v[2][x].push_back(y-1);
	v[3][x].push_back(y);
	v[3][x-1].push_back(y);
	v[3][x].push_back(y-1);
	v[3][x-1].push_back(y-1);
}
signed main(){
	scanf("%lld%lld%lld%lld%lld%lld%s",&r,&c,&m,&q,&sr,&sc,s+1);
	sr--;
	sc--;
	mxx=mnx=sr;
	mxy=mny=sc;
	ad(sr,sc);
	for(int i=1;i<=m;i++){
		if(s[i]=='N')
			sr--;
		if(s[i]=='S')
			sr++;
		if(s[i]=='E')
			sc++;
		if(s[i]=='W')
			sc--;
		ad(sr,sc);
		mxx=max(mxx,sr);
		mnx=min(mnx,sr);
		mxy=max(mxy,sc);
		mny=min(mny,sc);
	}
	for(int i=0;i<4;i++)
		for(int j=0;j<=r;j++){
			sg[i].rt[j+1]=sg[i].rt[j];
			int le=v[i][j].size();
			for(int k=0;k<v[i][j].size();k++)
				a[k]=v[i][j][k];
			sort(a,a+le);
			le=unique(a,a+le)-a;
			for(int k=0;k<le;k++)
				sg[i].ad(sg[i].rt[j+1],sg[i].rt[j+1],0,c,a[k]);
		}
	for(int i=1;i<=q;i++){
		int a,b,c,d;
		scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
		a--;
		b--;
		c--;
		d--;
		int ans=(c-a+1)*(d-b+1)-sg[0].qq(a+1,c+1,b+1,d+1);
		ans-=(c-a)*(d-b+1)-sg[1].qq(a+1,c,b+1,d+1);
		ans-=(c-a+1)*(d-b)-sg[2].qq(a+1,c+1,b+1,d);
		ans+=(c-a)*(d-b)-sg[3].qq(a+1,c,b+1,d);
		if(a<mnx&&mxx<c&&b<mny&&mxy<d)
			ans++;
		printf("%lld
",ans);
	}
}

扫除
https://www.cnblogs.com/ctmlpfs/p/13858306.html

小球进洞
一个和网上题解不同,个人认为比较巧妙的理解方式:
把洞看为(,把小球看为),则问题转化成:
设一个小球的位置为(a_i),它匹配的位置为(b_i),则询问

绯色IOI(悬念)
题目的(min(j-a_i,a_i-j))中,(j-a_i)(a_i-j)互为相反数。设为(b_i)(n-b_i)
题目要求匹配的限制:
(j-a_i=b_i)就是(j=a_i+b_i)
(a_i-j=b_i)就是(j=a_i-b_i)
(j-a_i=-b_i)就是(j=a_i-b_i)
(a_i-j=-b_i)就是(j=a_i+b_i)
综上,(j)最多只有(2)种取值。
把这两种取值在右边连一条边,则每个二分图左边点最多会产生1条边的贡献。
根据hall定理,二分图左边每个集合(S)覆盖右边的集合(R(S))(card(R(s))>=card(S))。所以右边的每个连通块都是树或者基环树。
每个左边的点最多和一个右边点进行匹配。假设一个左边的点连接右边的第一种权值的点(a)获得权值(v1),连接(b)获得(v2)
则再把(a o b)连接边权为(v1)的边,(b o a)连接边权为(v2)的边。
这两条边能选且只能选一条。选了以后获得权值的收益。
且在新图中,每个点的入度最多为1。

题目要求收益最大。
考虑对树/基环树分类讨论。
一棵树

原文地址:https://www.cnblogs.com/ctmlpfs/p/13858571.html