题解:P14598 [COCI 2025/2026 #2] 搭塔 / Tornjevi

题目大意

原题链接:https://www.luogu.com.cn/problem/P14598
题解:https://www.luogu.com.cn/article/f8ame8nw

给你一个 01 序列,然后对于一个点,它只能从前面选一个与它不同的点连接。问最少有几条链在一个区间内。\(q\) 个询问,问一段区间 \((l,r)\) 的最小值。

题目分析

首先贪心的想,对于肯定是能配对的配对。就是一个点只能连向前面最近的异点。

证明:
在一个区间内,如果要把原先的两个链给掰开,那么贡献最多是:\(ans=ans-1+1\)。不会更优。

解析都在代码里。

CODE

因为不会树桩数组太爱线段树了,于是打了一坨线段树。谨慎观看,主要看一下思路就行了。

#include<bits/stdc++.h>
#define wk(x) write(x),putchar(' ')
#define wh(x) write(x),putchar('\n')
#define L (p<<1)
#define R (L|1)
#define MID ((l+r)>>1)
#define N 200005
#define ll long long
using namespace std;
int n,m,k,jk,ans,sum,num,cnt,tot;
int kis[2][N],dis[N],lst[N],wis[N],q[N];
char a[N];

int read(int &x){
	x=0;int ff=1;char ch=getchar();
	while(!(ch>='0'&&ch<='9')){ff=(ch=='-'?-1:ff);ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	x*=ff;return x;
}

void write(int x){
	if(x<0){x=-x;putchar('-');}
	if(x>=10) write(x/10);
	putchar('0'+(x%10));return;
}

struct SEG{
	struct T{
		int l,r,ans;
	}T[N<<2];
	
	void G(int p){
		T[p].ans=T[L].ans+T[R].ans;
		return;
	}
	
	void build(int p,int l,int r){
		T[p].l=l;T[p].r=r;
		if(l==r){T[p].ans=0;return;}
		build(L,l,MID),build(R,MID+1,r);
		G(p);return;
	}
	
	int qry(int p,int l,int r,int x,int y){
		if(x<=l&&r<=y) return T[p].ans;int z=0;
		if(MID>=x) z+=qry(L,l,MID,x,y);
		if(MID<y) z+=qry(R,MID+1,r,x,y);
		G(p);return z;
	}
	
	void cge(int p,int l,int r,int x,int z){
		if(l==r){T[p].ans+=z;return;}
		if(MID>=x) cge(L,l,MID,x,z);
		else cge(R,MID+1,r,x,z);
		G(p);return;
	}
}T;
//线段树维护区间和
//当前每一种颜色最后一次出现的位置权值唯一

struct P{
	int l,r,z,id;
}v[N];

bool cmp(P a,P b){
	return a.r<b.r||(a.r==b.r&&a.l>b.l);
}//按区间(第一关键字以右端点)端点排

bool cmp1(P a,P b){
	return a.id<b.id;
}//按询问顺序来排

signed main(){
	read(n),read(m);scanf("%s",a+1);
	T.build(1,1,n);//建树
	for(int i=1;i<=n;i++){
		int x=a[i]=='P'?1:0;//转化成01序列
  //kis[x][0] 当前所有链中以x(0/1)为结尾的链的个数。kis[x][1]同理。
  //kis[x][y] 当前所有链中以x(0/1)为结尾的第i个链的颜色。
    //因为一定要不同才能连接,故有x^1
		if(kis[x^1][0]){//前面有满足可链接的情况
			wis[i]=kis[x^1][kis[x^1][0]];
      //随便选一个点来连接,我这里用了最后一个点,方便维护
			kis[x^1][0]--;kis[x][0]++;
			kis[x][kis[x][0]]=wis[i];
      //将贡献转换
		}else{//没有可匹配的点
			wis[i]=kis[x][++kis[x][0]]=++cnt;
      //新开一条链
		}
	}
	for(int i=1;i<=m;i++) read(v[i].l) ,read(v[i].r),v[i].id=i;
	sort(1+v,1+v+m,cmp);int l=1;//排序
	for(int i=1;i<=n;i++){
		if(lst[wis[i]]) T.cge(1,1,n,lst[wis[i]],-1);
		T.cge(1,1,n,i,1);lst[wis[i]]=i;
    //更新每种颜色的最后位置
    //考虑为什么这样只是对的:对于每种颜色记录最后的位置,那么就不会有重复算的情况。一个颜色在区间内存在,那么这种颜色的最后一个位置也一定就在这个区间内。但是如果区间前也有,但如果只算最后一个,就不会重复减去贡献。
		while(v[l].r==i){//已经排好序了,按顺序遍历
			v[l].z=T.qry(1,1,n,v[l].l,v[l].r);
			l++;//枚举询问
		}
	}
	sort(1+v,1+v+m,cmp1);
	for(int i=1;i<=m;i++) wh(v[i].z);
	return 0;
}
posted @ 2025-11-26 13:36  Red_river_hzh  阅读(0)  评论(0)    收藏  举报