题解: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;
}
本文来自博客园,作者:Red_river_hzh,转载请注明原文链接:https://www.cnblogs.com/Red-river-hzh/p/19272506

浙公网安备 33010602011771号