WaWa的奇妙冒险(第十五周集训自闭现场)

第十五周周记(线段树入门啦)

    • (一)线段树

(一)线段树

线段树的详解此处不做赘述,因为网上已经有讲的很详细的讲解了,我不觉得我可以讲的更好(更何况我还是一个灵魂画手。。。)
https://www.cnblogs.com/AC-King/p/7789013.html 这篇blog讲的很详细,还在慢慢学习中,内容很丰富
https://www.cnblogs.com/blvt/p/7905687.html 一个简单易懂的离散化,也贴在前面

题目记录:
**POJ - 2528 Mayor’s posters **
题意:有N个候选人,按输入循序张贴海报,问最后能看见的海报有多少张。

思路:因为长度的数据规模很大,不能直接用长度建立线段树,会mle,所以考虑对所有线段的端点离散化一遍,注意:此处要离散化的是线段,对于非相邻的线段,中间是需要空出来的,比如1 5,2 10,8 15,这种离散如果直接用unique离散化,会导致结果不正确(虽然能过这题),所以考虑离散化两次

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 2e5+5;

int sum[maxn << 2];
int a[maxn],l[maxn],r[maxn],q[maxn << 1],ans,tot;
bool vis[maxn << 2];

inline void pushdown(int rt){
	if(sum[rt]){
		sum[rt << 1] = sum[rt];
		sum[rt << 1|1] = sum[rt];
		sum[rt] = 0;
	}
	return ;
}

inline void build(int l,int r,int rt){
	sum[rt] = 0;
	if(l == r) return ;
	int m = (l+r) >> 1;
	build(l,m,rt << 1);
	build(m+1,r,rt << 1|1);
	return ;
}

inline void update(int L,int R,int l,int r,int rt,int val){
	if(L <= l && r <= R){
		sum[rt] = val;
		return ;
	}
	pushdown(rt);
	int m = (l+r) >> 1;
	if(L <= m) update(L,R,l,m,rt << 1,val);
	if(R > m) update(L,R,m+1,r,rt << 1|1,val);
	return ;
}

inline void query(int L,int R,int l,int r,int rt){
	if(l == r){
		if(!vis[sum[rt]] && sum[rt]){
			ans++;
			vis[sum[rt]] = 1;
		}
		return ;
	}
	pushdown(rt);
	int m = (l+r) >> 1;
	query(L,R,l,m,rt << 1);
	query(L,R,m+1,r,rt << 1|1);
	return ;
}

void init(){
	ans = tot = 0;
	memset(vis,0,sizeof(vis));
	memset(sum,0,sizeof(sum));
	return ;
}

void read(int n){
	for(int i = 1;i <= n;++i){
		scanf("%d%d",&l[i],&r[i]);
		a[tot++] = l[i];
		a[tot++] = r[i];
	}
	return ;
}

inline int ls(int n){
	sort(a,a+tot);
	int tot2 = 1;
	for(int i = 1;i < tot;++i) if(a[i] != a[i-1]) a[tot2++] = a[i];
	tot = tot2;
	for(int i = 1;i < tot2;++i) if(a[i] != a[i-1]+1) a[tot++] = a[i-1]+1;
	sort(a,a+tot);
	
	for(int i = 1;i <= n;++i){
		int left = lower_bound(a,a+tot,l[i]) - a;
		int right = lower_bound(a,a+tot,r[i]) - a;
		update(left+1,right+1,1,tot,1,i);
	}
	return tot;
}

int main()
{
	int n,t,val;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		init();
		read(n);
		int len = ls(n);
		query(1,len,1,len,1);
		printf("%d\n",ans);
	}
	return 0;
}

**HDU - 1394 Minimum Inversion Number **
题意:逐步队首元素移到队尾,问所有序列中,逆序对最小的序列有多少个逆序对

思路:我们可以用hash的思想,对于每个已经访问过的元素,在线段树下标处标记为1,当访问到新的数时,查询下标大于该元素的值有多少个即可(区间求和思想)。然后枚举队首元素,发现每个队首元素对于当前逆序对总数的贡献为队首元素值减一(因为后面有k-1个小于队首元素的值),当其移到队尾之后,其贡献变为n-k-1。因此可以在得到第一个序列的逆序对总数之后在线性复杂度内求解每个变形序列的逆序对总数。

#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 1e4+10;

int sum[maxn << 2],x,add[maxn << 2],a[maxn];

inline void pushup(int rt){
	sum[rt] = sum[rt << 1] + sum[rt << 1|1];
	return ;
}

inline void pushdown(int rt,int ln,int rn){
	if(add[rt]){
		add[rt << 1] += add[rt];
		add[rt << 1|1] += add[rt];
		sum[rt << 1] += add[rt]*ln;
		sum[rt << 1|1] += add[rt]*rn;
		add[rt] = 0;
	}
	return ;
}

inline void build(int l,int r,int rt){
	sum[rt] = 0;
	if(l == r){
		return ;
	}
	int m = (l+r) >> 1;
	build(l,m,rt << 1);
	build(m+1,r,rt << 1|1);
	pushup(rt);
	return ;
}

inline void update(int L,int l,int r,int rt,int val){
	if(l == r){
		sum[rt] += val;
		return ;
	}
	int m = (l+r) >> 1;
	if(L <= m) update(L,l,m,rt << 1,val);
	else update(L,m+1,r,rt << 1|1,val);
	pushup(rt);
	return ;
}

inline int query(int L,int R,int l,int r,int rt){
	if(L <= l && r <= R) return sum[rt];
	int m = (l+r) >> 1;
	ll ans = 0;
	pushdown(rt,m-l+1,r-m);
	if(L <= m) ans += query(L,R,l,m,rt << 1);
	if(R > m) ans += query(L,R,m+1,r,rt << 1|1);
	return ans;
}

int main()
{
	int n;
	while(~scanf("%d",&n)){
		build(0,n-1,1);
		int ans = 0;
		for(int i = 1;i <= n;++i){
			scanf("%d",&a[i]);
			ans += query(a[i],n-1,0,n-1,1);
			update(a[i],0,n-1,1,1);
		}
		
		int mins = ans;
		for(int i = 1;i <= n;++i){
			ans += n-2*a[i]-1;
			if(ans < mins) mins = ans;
		}
		
		printf("%d\n",mins);
	} 
	return 0;
}

HDU - 2795 Billboard
题意:一块板子,从左上开始往后下贴纸条,纸条大小必为1*n,对于每个贴的纸条确认其贴在哪一行,如果没位置贴,输出-1。

思路:以行数建立线段树,值为每行的长度,线段树维护区间最大值,对于每个纸条,优先找最右边可以贴下的位置(即最高处)。因为要贴的纸条不多,但给的板子非常大,为了不mle,要根据输入数据的情况建树。

#include 
#include 
#include 
#include 
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 2e5+5;

int tree[maxn << 2];

void pushup(int rt){
	tree[rt] = max(tree[rt << 1],tree[rt << 1|1]);
	return ;
}

void bulid(int l,int r,int rt,int w){
	if(l == r){
		tree[rt] = w;
		return ;
	}
	int m = (l+r) >> 1;
	bulid(l,m,rt << 1,w); 
	bulid(m+1,r,rt << 1|1,w);
	pushup(rt);
	return ;
}

inline int query(int l,int r,int rt,int x){
	if(tree[rt] < x) return -1;
	if(l == r){
		tree[rt] -= x;
		return l;
	}
	int m = (l+r) >> 1,pos;
	if(tree[rt << 1] >= x) pos = query(l,m,rt << 1,x);
	else if(tree[rt << 1|1] >= x) pos =  query(m+1,r,rt << 1|1,x);
	pushup(rt);
	return pos;
}

int main()
{
	int x,h,w,n;
	while(~scanf("%d%d%d",&h,&w,&n)){
		int k = min(h,n);
		bulid(1,k,1,w);
		for(int i = 1;i <= n;++i){
			scanf("%d",&x);
			printf("%d\n",query(1,k,1,x));
		}
	}
	return 0;
}

POJ - 1195 Mobile phones
题意:给出一个矩阵,可以单点更新和查询矩阵和

思路:二维线段树单点更新裸题,看了一下树上建树的模板,写了之后发现mle???后来实验发现,这题样例水了,局部更新可以防止mle的情况,但遇到情况还是会mle,正解应该是二维树状数组。

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 1030;

int tree[maxn << 2][maxn << 2];
int s,cmd;

void buildY(int rtX,int rtY,int l,int r){
	tree[rtX][rtY] = 0;
	if(l == r) return ;
	int m = (l+r) >> 1;
	buildY(rtX,rtY,l,m);
	buildY(rtX,rtY,m+1,r);
	return ;
}

void buildX(int rtX,int l,int r){
	buildY(rtX,1,l,r);
	if(l == r) return ;
	int m = (l+r) >> 1;
	buildX(rtX << 1,l,m);
	buildX(rtX << 1|1,m+1,r);
	return ;
}

void updateY(int rtX,int l,int r,int rtY,int val,int posy){
	tree[rtX][rtY] += val;
	if(l == r) return ;
	int m = (l+r) >> 1;
	if(posy <= m) updateY(rtX,l,m,rtY << 1,val,posy);
	else updateY(rtX,m+1,r,rtY << 1|1,val,posy);
	return ;
}

void updateX(int rtX,int l,int r,int val,int posx,int posy){
	updateY(rtX,0,s-1,1,val,posy);
	if(l == r) return ;
	int m = (l+r) >> 1;
	if(posx <= m) updateX(rtX << 1,l,m,val,posx,posy);
	else updateX(rtX << 1|1,m+1,r,val,posx,posy);
	return ;
}

inline int queryY(int rtX,int rtY,int l,int r,int b,int t){
	if(l >= b && r <= t) return tree[rtX][rtY];
	int m = (l+r) >> 1,ans = 0;
	if(b <= m) ans += queryY(rtX,rtY << 1,l,m,b,t);
	if(t > m) ans += queryY(rtX,rtY << 1|1,m+1,r,b,t);
	return ans;
}

inline int queryX(int rtX,int l,int r,int b,int t,int xl,int xr){
	if(l >= xl && r <= xr){
		return queryY(rtX,1,0,s-1,b,t);
	}
	int m = (l+r) >> 1,ans = 0;
	if(xl <= m) ans += queryX(rtX << 1,l,m,b,t,xl,xr);
	if(xr > m) ans += queryX(rtX << 1|1,m+1,r,b,t,xl,xr);
	return ans;
}

int main()
{
	int x,y,val,l,r,b,t;
	while(scanf("%d",&cmd) && cmd != 3){
		if(cmd == 0){
			scanf("%d",&s);
			buildX(1,0,s-1);
		}
		else if(cmd == 1){
			scanf("%d%d%d",&x,&y,&val);
			updateX(1,0,s-1,val,x,y);
		}
		else if(cmd == 2){
			scanf("%d%d%d%d",&l,&b,&r,&t);
			printf("%d\n",queryX(1,0,s-1,b,t,l,r));
		}
	}
	return 0;
}

POJ - 2299 Ultra-QuickSort
题意:问几次交换位置之后可以得到从小到大的有序队列(相邻交换)。

思路:推了一下,发现就是求逆序对的数量,用前面求逆序对数量的建树方式即可,但数据较大,需要离散化一下。

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 5e5+10;
typedef long long ll;

int tree[maxn << 2],a[maxn],ls[maxn],b[maxn];

inline int lsh(int n){
	sort(a+1,a+n+1);
	int cnt = unique(a+1,a+n+1)-a-1;
	return cnt;
}

void pushup(int rt){
	tree[rt] = tree[rt << 1] + tree[rt << 1|1];
	return ;
}

void build(int rt,int l,int r){
	tree[rt] = 0;
	if(l == r) return ;
	int m = (l+r) >> 1;
	build(rt << 1,l,m);
	build(rt << 1|1,m+1,r);
	return ;
}

void update(int rt,int pos,int l,int r){
	if(l == r){
		tree[rt]++;
		return ;
	}
	int m = (l+r) >> 1;
	if(pos <= m) update(rt << 1,pos,l,m);
	else update(rt << 1|1,pos,m+1,r);
	pushup(rt);
	return ;
}

inline ll query(int rt,int L,int R,int l,int r){
	if(L <= l && r <= R) return tree[rt];
	int m = (l+r) >> 1;
	ll ans = 0;
	if(L <= m) ans += query(rt << 1,L,R,l,m);
	if(R > m) ans += query(rt << 1|1,L,R,m+1,r);
	return ans;
}

int main()
{
	int n;
	while(scanf("%d",&n) && n){
		for(int i = 1;i <= n;++i) scanf("%d",&a[i]),b[i] = a[i];
		int cnt = lsh(n);
		build(1,1,cnt);
		
		ll ans = 0;
		for(int i = 1;i <= n;++i){
			int pos = lower_bound(a+1,a+cnt+1,b[i])-a;
			update(1,pos,1,cnt);
			ans += query(1,pos+1,cnt,1,cnt);
		}
		
		printf("%lld\n",ans);
	}
	return 0;
}

POJ - 2828 Buy Tickets
题意:每个人会要求插在队列的某人位置(第几个人后面),数据保证能够正常实现插队。

思路:刚开始做毫无思路,无奈瞅了瞅题解,题解给出的方式是从后往前逐步确认每个人的位置,因为后来选择某位置的人,必定是最终在该位置的人。而先来的会随后面相同位置的人的增多,而后退n个周期。

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 2e5+10;
typedef long long ll;

int sum[maxn << 2],v[maxn << 2],p[maxn],k[maxn];
int x;
bool f;

void pushup(int rt){
	sum[rt] = sum[rt << 1] + sum[rt << 1|1];
	return ;
}

void build(int rt,int l,int r){
	v[rt] = 0;
	if(l == r){
		sum[rt] = 1;
		return ;
	}
	int m = (l+r) >> 1;
	build(rt << 1,l,m);
	build(rt << 1|1,m+1,r);
	pushup(rt);
	return ;
}

void update(int rt,int pos,int l,int r,int val){
	if(l == r){
		sum[rt] = 0;
		v[rt] = val;
		return ;
	}
	int m = (l+r) >> 1;
	if(pos < sum[rt << 1]) update(rt << 1,pos,l,m,val);
	else update(rt << 1|1,pos-sum[rt << 1],m+1,r,val);
	pushup(rt);
	return ;
}

void print(int rt,int l,int r){
	if(l == r){
		printf("%d ",v[rt]);
		return ;
	}
	int m = (l+r) >> 1;
	print(rt << 1,l,m);
	print(rt << 1|1,m+1,r);
	return ;
}

int main()
{
	int n;
	while(~scanf("%d",&n)){
		build(1,1,n);
		for(int i = 1;i <= n;++i) scanf("%d%d",&p[i],&k[i]);
		for(int i = n;i >= 1;--i) update(1,p[i],1,n,k[i]);
		print(1,1,n);
		printf("\n");
	}
	return 0;
}

POJ - 2352 Stars
题意:每个星星的级别由其左边和正下方的星星数量决定,统计每个等级的星星有多少颗。

思路:因为题目很友好,给出的星星位置是按x,y的升序给出的,所以我们可以每加入一颗星星统计一次数量即可,以x轴为区间,星星数量为权值,建立线段树。

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 3e4+2010;
typedef long long ll;

int sum[maxn << 2],ans[maxn];

void pushup(int rt){
	sum[rt] = sum[rt << 1] + sum[rt << 1|1];
	return ;
}

void build(int rt,int l,int r){
	sum[rt] = 0;
	if(l == r) return ;
	int m = (l+r) >> 1;
	build(rt << 1,l,m);
	build(rt << 1|1,m+1,r);
	return ;
}

void update(int rt,int pos,int l,int r){
	if(l == r){
		sum[rt]++;
		return ;
	}
	int m = (l+r) >> 1;
	if(pos <= m) update(rt << 1,pos,l,m);
	else update(rt << 1|1,pos,m+1,r);
	pushup(rt);
	return ;
}

inline int query(int rt,int L,int R,int l,int r){
	if(L <= l && r <= R) return sum[rt];
	int ans = 0;
	int m = (l+r) >> 1;
	if(L <= m) ans += query(rt << 1,L,R,l,m);
	if(R > m) ans += query(rt << 1|1,L,R,m+1,r);
	return ans;
}

int main()
{
	int n,x,y;
	while(~scanf("%d",&n)){
		build(1,0,maxn);
		memset(ans,0,sizeof(ans));
		for(int i = 1;i <= n;++i){
			scanf("%d%d",&x,&y);
			ans[query(1,0,x,0,maxn)]++;
			update(1,x,0,maxn);
		}
		for(int i = 0;i < n;++i) printf("%d\n",ans[i]);
	}
	return 0;
}

POJ - 2750 Potted Flower
题意:一个环,每个位置都有权值,要求你找出权值最大的弧权值为多少(弧不能连成圆)

思路:一开始同样毫无思路,无奈看题解,说是一道线段树上玩dp的题,大概看懂了,所以来扯一扯。
首先,在做这一题前要对归并求最长子串和有一个认识或者写过CH4301这道题也是可以的。

现在把问题转化为大致两种情况:
1.环上所有权值都为正数
对于这种情况,我们只需要求和在减去环上的一个最小权值即可
2.环上存在负权结点
对于这种情况,最大值可能是我们求出的最大子串和,或者是环状结构里,总和去掉最小子串和(说的有点绕,大概脑补一下图应该是可以想通的)

好了,分析完了,接下来就是怎么处理了。
区间总和:只要通过区间求和即可求得
最大子串和:参考CH4301,维护包含左边界的最大值和维护包含右边界的最大值,讲其与左子树最大以及右子树最大比较即可。(对于归并求最大子串和,我们要考虑三个方面,最大值在左边、右边,或者横跨两个区间)
最小子串和:参照最大子串和的思路即可

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 1e5+10;
typedef long long ll;

int sum[maxn << 2],lmax[maxn << 2],rmax[maxn << 2],Max[maxn << 2],lmin[maxn << 2],rmin[maxn << 2],Min[maxn << 2];
int x;

void pushup(int rt){
	sum[rt] = sum[rt << 1]+sum[rt << 1|1];
	lmax[rt] = max(lmax[rt << 1],sum[rt << 1]+lmax[rt << 1|1]);
	lmin[rt] = min(lmin[rt << 1],sum[rt << 1]+lmin[rt << 1|1]);
	rmax[rt] = max(rmax[rt << 1|1],sum[rt << 1|1]+rmax[rt << 1]);
	rmin[rt] = min(rmin[rt << 1|1],sum[rt << 1|1]+rmin[rt << 1]);
	Max[rt] = max(max(Max[rt << 1],Max[rt << 1|1]),lmax[rt << 1|1]+rmax[rt << 1]);
	Min[rt] = min(min(Min[rt << 1],Min[rt << 1|1]),lmin[rt << 1|1]+rmin[rt << 1]);
	return ;
}

void build(int rt,int l,int r){
	if(l == r){
		scanf("%d",&x);
		sum[rt] = Max[rt] = Min[rt] = lmax[rt] = rmax[rt] = lmin[rt] = rmin[rt] = x;
		return ;
	}
	int m = (l+r) >> 1;
	build(rt << 1,l,m);
	build(rt << 1|1,m+1,r);
	pushup(rt);
	return ;
}

void update(int rt,int pos,int l,int r,int val){
	if(l == r){
		sum[rt] = Max[rt] = Min[rt] = lmax[rt] = rmax[rt] = lmin[rt] = rmin[rt] = val;
		return ;
	}
	int m = (l+r) >> 1;
	if(pos <= m) update(rt << 1,pos,l,m,val);
	else update(rt << 1|1,pos,m+1,r,val);
	pushup(rt);
	return ;
}

int main()
{
	int n,q,val,pos;
	scanf("%d",&n);
	build(1,1,n);
	scanf("%d",&q);
	while(q--){
		scanf("%d%d",&pos,&val);
		update(1,pos,1,n,val);
		printf("%d\n",sum[1] == Max[1] ?Max[1]-Min[1] :max(Max[1],sum[1]-Min[1]));
	}
	return 0;
}

**Count Color **
题意:区间染色,刚开始全是颜色1,并且会在中途查询区间内有多少种颜色。

思路:注意颜色上限为30种,此处很容易就能想到二进制hash的思想,然后使用或运算更新结点状态即可,求多少种颜色时就可以直接算了。

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 1e5+10;
typedef long long ll;

int sum[maxn << 2],add[maxn << 2];

void pushup(int rt){
	sum[rt] = sum[rt << 1]|sum[rt << 1|1];
	return ;
}

void pushdown(int rt){
	if(add[rt]){
		add[rt << 1] = add[rt];
		add[rt << 1|1] = add[rt];
		sum[rt << 1] = add[rt];
		sum[rt << 1|1] = add[rt];
		add[rt] = 0;
	}
	return ;
}

void build(int rt,int l,int r){
	sum[rt] = 1;
	add[rt] = 0;
	if(l == r) return ;
	int m = (l+r) >> 1;
	build(rt << 1,l,m);
	build(rt << 1|1,m+1,r);
	pushup(rt);
	return ;
}

void update(int rt,int L,int R,int l,int r,int val){
	if(L <= l && r <= R){
		add[rt] = sum[rt] = val;
		return ;
	}
	pushdown(rt);
	int m = (l+r) >> 1;
	if(L <= m) update(rt << 1,L,R,l,m,val);
	if(R > m) update(rt << 1|1,L,R,m+1,r,val);
	pushup(rt);
	return ;
}

inline int query(int rt,int L,int R,int l,int r){
	if(L <= l && r <= R) return sum[rt];
	pushdown(rt);
	int ans = 0;
	int m = (l+r) >> 1;
	if(L <= m) ans |= query(rt << 1,L,R,l,m);
	if(R > m) ans |= query(rt << 1|1,L,R,m+1,r);
	return ans;
}

inline int cal(int k){
	int ans = 0;
	while(k){
		k -= k&(-k);
		ans++;
	}
	return ans;
} 

int main()
{
	int L,T,O,l,r,c;
	char cmd;
	while(~scanf("%d%d%d",&L,&T,&O)){
		build(1,1,L);
		for(int i = 1;i <= O;++i){
			scanf(" %c",&cmd);
			if(cmd == 'C'){
				scanf("%d%d%d",&l,&r,&c);
				if(l > r) swap(l,r);
				update(1,l,r,1,L,1 << (c-1));
			}
			else{
				scanf("%d%d",&l,&r);
				if(l > r) swap(l,r);
				printf("%d\n",cal(query(1,l,r,1,L)));
			}
		}
	}
	return 0;
}

你可能感兴趣的:(萌新级)