题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6406
题目大意:给出一个序列,让你从头遍历到末尾,第一个数必须选,后面的,没碰到一个数,如果这个数大于前面选择的那个数,就再次选择这个数,给出m次操作,将x位置的数修改为val,并查询修改后能选择多少个数?(每次操作是独立的,即对序列不产生影响)。
思路:比赛时没有想出来,只想着用前缀和什么的,似乎用二分,但是由于无序也不知道怎么二分。之后看题解直到怎么二分了,解锁二分新姿势。
有多种解题方法,看着都不难。(标程一个不会,整天就搞些骚东西QAQ....)
目录
2.延伸的线段树+二分(既然ST表都能用,那线段树肯定也能用啦)
1.标称为ST表+二分
思路:未实现
2.延伸的线段树+二分(既然ST表都能用,那线段树肯定也能用啦)
思路:未实现
3.整体二分+局部二分(实现细节比较多)(已实现)
思路:首先我们根据初试序列得到一个答案序列B(即完全递增序列),然后得到B中元素多对应原数组A中的位置数组Pos。然后根据Pos,对每个小区间在进行依次获得小答案序列Vec[Pos],便于后面进行局部二分。
然后我们每次修改的时候会有以下几种情况:
1.修改处于B数组中的元素:有一下几种情况:
a.将该元素变大,那么会对后面的递增序列产生影响(可能会挤掉一些元素)因此我们直接减去影响的区间即可。
b.将该元素变小,那么会对后面的递增序列产生影响(可能会增加一些元素),因此我们二分之前得到的局部区间即可找到增加的元素的个数。当然,可能变得比前面的有效元素还要小,那么我们比较一下,选更大的那个即可(因为只影响B,A中的无效元素可以说都小于max(val,B[qPos-1]))。
2.修改不处于B数组中的元素:有以下几种情况:
a.将该元素变大同1a。注意由于此时修改的不是B中的元素,因此qPos位于x之前。
b.将该元素变小,本来就没有影响,因为都变小了,所以更没有影响啦!。
说的可能不是很清楚,给组样例说明一下吧:这组样例,我们来修改一下:
4 5:
4 7:掠过6,Ans=4-(4-1-(2+1))=4
4 8:掠过6,8:Ans=(4-(5-1-(2+1))=3
3 1:变小了:修改后面的数组:resB: 2 3 4 6 8,减少4新增3 4,Ans=5.
3 5:同理
.......
由于细节较多,我就在代码中进行注释了:
ACCode:400+ms,还挺快
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<map>
#include<set>
#include<deque>
#include<queue>
#include<stack>
#include<bitset>
#include<string>
#include<fstream>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
#define Pair pair<int,int>
//#define max(a,b) (a)>(b)?(a):(b)
//#define min(a,b) (a)<(b)?(a):(b)
#define clean(a,b) memset(a,b,sizeof(a))// ??
//std::ios::sync_with_stdio(false);
// register
const int MAXN=1e5+10;
const int INF32=0x3f3f3f3f;
const ll INF64=0x3f3f3f3f3f3f3f3f;
const ll mod=1e9+7;
const double PI=acos(-1.0);
const double EPS=1.0e-8;
int A[MAXN];
int B[MAXN],Pos[MAXN],tot;
vector<int> Vec[MAXN];
int n,m;
int FindSeq(int Key){
int l=1,r=tot,mid;
while(l<=r){
mid=(l+r)>>1;
if(Pos[mid]==Key) return 1;
else if(Pos[mid]<Key) l=mid+1;
else r=mid-1;
}return 0;
}
int GetBR(int Key){
int l=1,r=tot,mid;
while(l<=r){
mid=(l+r)>>1;
if(B[mid]>Key) r=mid-1;
else l=mid+1;
}return l;
}
int GetPos(int Key){
int l=1,r=tot,mid;
while(l<=r){
mid=(l+r)>>1;
if(Pos[mid]==Key) return mid;
else if(Pos[mid]>Key) r=mid-1;
else l=mid+1;
}return r;
}
int GetVec(int qPos,int Key){
int l=0,r=Vec[qPos].size()-1,mid;
while(l<=r){
mid=(l+r)>>1;
if(Vec[qPos][mid]>Key) r=mid-1;
else l=mid+1;
}return l;
}
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&A[i]);
}
tot=0;
for(int i=1;i<=n;++i){
if(i==1||A[i]>B[tot]){
B[++tot]=A[i];
Pos[tot]=i;
}
}B[++tot]=INF32;Pos[tot]=n+1;A[n+1]=INF32;
for(int i=1;i<=n;++i) Vec[i].clear();
for(int i=1;i<tot;++i){
if(Pos[i]+1<Pos[i+1]){
Vec[i].push_back(A[Pos[i]+1]);
for(int j=Pos[i]+2;j<Pos[i+1];++j){
if(A[j]>Vec[i][Vec[i].size()-1]){
Vec[i].push_back(A[j]);
}
}
}
}
// printf("B[]: ");for(int i=1;i<=tot;++i) printf("%d ",B[i]);printf("\n");
// printf("Pos: ");for(int i=1;i<=tot;++i) printf("%d ",Pos[i]);printf("\n");
for(int i=1;i<=m;++i){
int x,val;scanf("%d%d",&x,&val);
if(FindSeq(x)){//在递增序列中
if(A[x]<val){//变大 对后面产生影响 可以将后面的剔除出去
int qPos=GetPos(x);//获得当前位置
int qBR=GetBR(val);//获得后面 > val的位置
// printf("在递增序列中&&变大:tot=%d qPos=%d qBR=%d\n",tot,qPos,qBR);
//此时,[qPos,qBR-1]中的所有元素都变成了val
printf("%d\n",tot-1-(qBR-1-qPos));
continue;
}
//变小||不变,当前位置作废,相当于重新在[Pos-1,Pos+1]之间插入一个val
int qPos=GetPos(x);//获得当前位置
if(val>B[qPos-1]){//仍然大于前面的那个元素
int qBR=GetVec(qPos,val);qBR=Vec[qPos].size()-qBR;
//此时,[qBR,Vec[qPos].size()]中都是新加入的 .
// printf("在递增序列中&&变小,但还大:tot=%d Add=%d\n",tot,qBR+1);
printf("%d\n",tot-1+qBR);
continue;
}
//小于等于前面那个元素
int qBR=GetVec(qPos,B[qPos-1]);qBR=Vec[qPos].size()-qBR;
// printf("在递增序列中&&小于前面元素: tot=%d Add=%d\n",tot,qBR);
printf("%d\n",tot-1+qBR-1);
continue;
}
//不在递增序列中
if(A[x]<val){//变大,可能对后面产生影响,可以将后面的顶掉
int qPos=GetPos(x);//获得前面的位置 x位于[Pos~Pos+1]区间内
int qBR=GetBR(val);//获得后面的 > val 的元素位置
if(val>B[qPos]){//大于前面的那个数,新增加一个
//此时,[qPos+1,qBR-1]中的所有元素都变成了val
// printf("不再递增序列中&&更大:tot=%d qPos=%d qBR=%d\n",tot,qPos,qBR);
printf("%d\n",tot-1-(qBR-1-qPos-1));
continue;
}
//小于前面的没影响
// printf("不在递增序列中&&比前面的小,无影响\n");
printf("%d\n",tot-1);
continue;
}
//变小||不变,没有影响
// printf("不在递增序列中&&变小,无影响\n");
printf("%d\n",tot-1);
}
}
}
/*
2
10 5
8 3 1 3 12 3 4 6 9 2
1 2
2 3
3 4
4 5
7 8
9 10
2 1 4 3 4 6 3 8 3
4 5
4 7
2 5
2 7
2 8
2 1
3 5
3 7
3 8
3 1
*/
4. dalao 的纯线段树做法(膜)
正解一个不会,满脑子骚方法,我凉了QAQ。。。
找ST表+二分的题解的时候,找到了个这个,看着思路似乎很简单,就试着写了些,T了几发,总算找到精髓了。膜神仙。
大佬:这不是BZOJ原题嘛!,大佬链接:https://blog.csdn.net/weixin_39453270/article/details/81784154
思路:建一棵树,每个节点维护区间最值和区间有效长度。然后就是对其更新和查询,
左节点最大值<=val只访问右节点。//意思是左节点维护的区间没有用,我们要重新对右节点进行维护,所以仍要访问。
否则 ans=右子树的有效长度(之前处理过了)+向左子树查找(log n)//由于右子树是根据左节点得出的,因此左子树有效的话,右子树就是之前处理的值,直接相加即可。然后只用维护左子树。
ACCode:900+ms,虽然慢,但是码量少,也好写,熟练的话10min+就出来了。果然当时看榜单的时候那个10min的可能就是这个方法了(毕竟BZOJ原题QAQ)
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<map>
#include<set>
#include<deque>
#include<queue>
#include<stack>
#include<bitset>
#include<string>
#include<fstream>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
#define Pair pair<int,int>
//#define max(a,b) (a)>(b)?(a):(b)
//#define min(a,b) (a)<(b)?(a):(b)
#define clean(a,b) memset(a,b,sizeof(a))// ??
//std::ios::sync_with_stdio(false);
// register
const int MAXN=1e5+10;
const int INF32=0x3f3f3f3f;
const ll INF64=0x3f3f3f3f3f3f3f3f;
const ll mod=1e9+7;
const double PI=acos(-1.0);
const double EPS=1.0e-8;
class Segment{
int Tree[MAXN<<2],Cnt[MAXN<<2];
public : void Build(int l,int r,int rt,int A[]){
if(l==r){
Tree[rt]=A[l];Cnt[rt]=1;
return ;
}
int mid=(l+r)>>1;
Build(l,mid,rt<<1,A);Build(mid+1,r,rt<<1|1,A);
Tree[rt]=max(Tree[rt<<1],Tree[rt<<1|1]);
Cnt[rt]=Cnt[rt<<1]+Query(l+1,r,Tree[rt<<1],mid+1,r,rt<<1|1);
}
public : void Update(int ql,int qr,int val,int l,int r,int rt){
if(ql<=l&&r<=qr){
Tree[rt]=val;Cnt[rt]=1;
return ;
}
int mid=(l+r)>>1;
if(ql<=mid) Update(ql,qr,val,l,mid,rt<<1);
if(qr>mid) Update(ql,qr,val,mid+1,r,rt<<1|1);
Tree[rt]=max(Tree[rt<<1],Tree[rt<<1|1]);
Cnt[rt]=Cnt[rt<<1]+Query(ql,qr,Tree[rt<<1],mid+1,r,rt<<1|1);
}
public : int Query(int ql,int qr,int maxx,int l,int r,int rt){
if(Tree[rt]<=maxx) return 0;
if(l==r) return 1;
int mid=(l+r)>>1;
if(Tree[rt<<1]<=maxx) return Query(ql,qr,maxx,mid+1,r,rt<<1|1);
return (Cnt[rt]-Cnt[rt<<1])+Query(ql,qr,maxx,l,mid,rt<<1);
}
};
int A[MAXN];
int n,m;
Segment Seg;
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&A[i]);
}
Seg.Build(1,n,1,A);
for(int i=1;i<=m;++i){
int x,val;scanf("%d%d",&x,&val);
Seg.Update(x,x,val,1,n,1);
printf("%d\n",Seg.Query(1,n,0,1,n,1));
Seg.Update(x,x,A[x],1,n,1);
}
}
}
/*
2
10 5
8 3 1 3 12 3 4 6 9 2
1 2
2 3
3 4
4 5
7 8
9 10
2 1 4 3 4 6 3 8 3
4 5
4 7
2 5
2 7
2 8
2 1
3 5
3 7
3 8
3 1
*/