2023ACM暑假训练day 8 线段树
DAY 8 线段树
训练地址:传送门
训练情况简介
2023-07-04
早上:AB
下午:C
晚上:DEF
AB 单点修改,区间查询最值和最大值
C 区间修改+离散化+整体查询
D 区间替换修改+整体查询
E 区间修改+区间查询(板)
F 区间修改->单点修改+优化,区间查询
早上学了普通的线段树,下午学了线段树的区间修改操作,晚上补了个人训练的E题,加深了线段树的印象
2023-07-04 22:45:54 星期二下机,明日继续
2023-07-05
早上:G
下午:HI
晚上:J
G 题推荐,练练线段树+二分优化DP
H 题线段树,单点修改+区间查询连续子区间最大和,很有意思
I 题与 H 题类似,也是求区间最大连续字段和,题好,但是有点题意还没读懂,日后不推荐二次做,推荐理解
J题两个线段树,一个区间和,一个区间最值(用来剪枝)
2023-07-06 09:25:42 星期四
早上:K
下午:L
K 题与J题类似,双线段树,一个区间和,一个区间最值(用来剪枝)
L 题需要一点思维,想出来之后就是简单的单点修改+区间查询最小值
M 题与HI题类似,是一个给定区间括号匹配数问题
A 题
hdu 1166 敌兵布阵
题意:
单点修改,区间求和
思路:
今天使用线段树解决问题,之前的day3训练使用了树状数组解决问题
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
单点修改,区间查询
*/
const int maxm=5e4+5,inf=0x3f3f3f3f,mod=998244353;
int n,seg[4*maxm],a[maxm];
string ss;
void push_up(int p){
seg[p]=seg[p*2]+seg[p*2+1];
return ;
}
void build(int p,int pl,int pr){
if(pl==pr){
seg[p]=a[pl];
a[pl]=p;
return ;
}
int mid=(pl+pr)>>1;
build(p*2,pl,mid);
build(p*2+1,mid+1,pr);
push_up(p);
return ;
}
void update(int p,int x){//单点修改
p=a[p];
while(p){
seg[p]+=x;
p/=2;
}
return ;
}
int query(int l,int r,int p,int pl,int pr){//区间查询
if(l<=pl&&r>=pr) return seg[p];
int mid=(pl+pr)>>1,res=0;
if(l<=mid) res+=query(l,r,p*2,pl,mid);
if(r>mid) res+=query(l,r,p*2+1,mid+1,pr);
return res;
}
void solve(){
int c;
cin>>c;
for(int z=1;z<=c;++z){
cout<<"Case "<<z<<":\n";
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
int x,y,ans;
while(cin>>ss){
if(ss=="End") break;
cin>>x>>y;
if(ss=="Add"){
update(x,y);
}else if(ss=="Sub"){
update(x,-y);
}else{//"Query"
ans=query(x,y,1,1,n);
cout<<ans<<'\n';
}
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
B 题
hdu 1754 I Hate It
题意:
单点修改,区间查询最大值
思路:
线段树板子即可
记得关流或者快读,不然会TLE
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
单点修改,区间查询最大值
*/
const int maxm=2e5+5,inf=0x3f3f3f3f,mod=998244353;
int n,m,a[maxm],seg[maxm*4];
void push_up(int p){
seg[p]=max(seg[p<<1],seg[(p<<1)+1]);
return ;
}
void build(int p,int pl,int pr){
if(pl==pr){
seg[p]=a[pl];
a[pl]=p;
return ;
}
int mid=(pl+pr)>>1;
build(p<<1,pl,mid);
build((p<<1)+1,mid+1,pr);
push_up(p);
return ;
}
void update(int x,int y){
x=a[x];
seg[x]=y;
x>>=1;
while(x){
push_up(x);
x>>=1;
}
return ;
}
int query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r) return seg[p];
int ans=0,mid=(pl+pr)>>1;
if(l<=mid) ans=max(ans,query(l,r,p<<1,pl,mid));
if(r>mid) ans=max(ans,query(l,r,(p<<1)+1,mid+1,pr));
return ans;
}
void solve(){
while(cin>>n){
cin>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
string ss;
int x,y,ans;
for(int i=0;i<m;++i){
cin>>ss>>x>>y;
if(ss=="Q"){
ans=query(x,y,1,1,n);
cout<<ans<<'\n';
}else{//"U"
update(x,y);
}
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
C 题
百炼oj 2528:Mayor's posters
题意:
给你n个区间,按照顺序给出在一面墙上贴海报(海报位置贴在区间[l,r]),问最后有多少张海报能显示出来
思路:
推荐题,区间修改
利用线段树区间修改完成区间的覆盖,最后统计数量即可
另外就是利用离散化的思想,使得区间的范围大大缩小,使得线段树可以实现
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
区间修改+离散化简化操作
最后整体查询
*/
const int maxm=1e4+5,maxn=2e4+5,inf=0x3f3f3f3f,mod=998244353;
int n,seg[maxn<<4],len,ans;
bool vis[maxn];
struct node{
int l,r;
}p[maxm];
int pre(){
int cnt=0;
for(int i=1;i<=n;++i){
cin>>p[i].l>>p[i].r;
seg[++cnt]=p[i].l;
seg[++cnt]=p[i].r;
seg[++cnt]=p[i].r+1;//防止出现[1,2][2,3][1,3]离散后三张海报变两张的情况
}
sort(seg+1,seg+1+cnt);
int l=unique(seg+1,seg+1+cnt)-seg-1;//去重
for(int i=1;i<=n;++i){
p[i].l=lower_bound(seg+1,seg+1+l,p[i].l)-seg;
p[i].r=lower_bound(seg+1,seg+1+l,p[i].r)-seg;
}
return l;
}
int ls(int p){ return p<<1; }
int rs(int p){ return p<<1|1; }
void push_up(int p){
seg[p]=seg[ls(p)]+seg[rs(p)];
return ;
}
void build(int p,int pl,int pr){//建树
seg[p]=0;
if(pl==pr){ return ; }
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
return ;
}
void push_down(int p,int pl,int pr){
if(seg[p]>0){//有海报时覆盖,不然会对原海报产生影响
seg[ls(p)]=seg[rs(p)]=seg[p];
seg[p]=0;
}
return ;
}
void update(int l,int r,int k,int p,int pl,int pr){
if(l<=pl&&pr<=r){
seg[p]=k;
return ;
}
push_down(p,pl,pr);//新的覆盖,让旧的覆盖push_down
int mid = (pl+pr)>>1;
if(l<=mid) update(l,r,k,ls(p),pl,mid);
if(mid<r) update(l,r,k,rs(p),mid+1,pr);
return ;
}
void query(int p,int pl,int pr){
if(seg[p]){//遇到已经覆盖了的海报就直接停止继续寻找的行为
if(!vis[seg[p]]){//遇到一个新的海报++ans
++ans;
vis[seg[p]]=true;
}
return ;
}
if(pl==pr) return ;
int mid=(pl+pr)>>1;
if(pl<=mid) query(ls(p),pl,mid);
if(mid<pr) query(rs(p),mid+1,pr);
return ;
}
int ask(int l,int r){
mem(vis,false);
ans=0;
vis[0]=true;//标记0不是海报
query(1,1,len);
return ans;
}
void solve(){
cin>>n;
len=pre();
build(1,1,len);//建树
for(int i=1;i<=n;++i){
update(p[i].l,p[i].r,i,1,1,len);
}
cout<<ask(1,len)<<'\n';
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--){
solve();
}
return 0;
}
D 题
hdu 1698 Just a Hook
题意:
给你一个长度为 n 的初始均为 1 的数组,每次选其中连续的一段进行替换成数字 1 或 2 或 3 ,问你最后整个数组的和是多少
思路:
线段树区间修改,整个区间查询
代码详见提交
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
区间替换修改+整体查询
*/
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
int n,a[maxm],seg[maxm<<2],q,tag[maxm<<2];
int ls(int p){ return p<<1; }
int rs(int p){ return p<<1|1; }
void push_up(int p){
seg[p]=seg[ls(p)]+seg[rs(p)];
return ;
}
void build(int p,int pl,int pr){
tag[p]=0;
if(pl==pr){
seg[p]=1;
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
void change(int p,int pl,int pr,int k){
seg[p]=(pr-pl+1)*k;
tag[p]=k;
return ;
}
void push_down(int p,int pl,int pr){
if(tag[p]){
int mid=(pl+pr)>>1;
change(ls(p),pl,mid,tag[p]);
change(rs(p),mid+1,pr,tag[p]);
tag[p]=0;
}
return ;
}
void update(int l,int r,int k,int p,int pl,int pr){
if(l<=pl&&pr<=r){
change(p,pl,pr,k);
return ;
}
push_down(p,pl,pr);
int mid=(pl+pr)>>1;
if(l<=mid) update(l,r,k,ls(p),pl,mid);
if(mid<r) update(l,r,k,rs(p),mid+1,pr);
push_up(p);
return ;
}
void solve(){
int cs;
cin>>cs;
for(int i=1;i<=cs;++i){
cin>>n;
build(1,1,n);
cin>>q;
int x,y,z;
while(q--){
cin>>x>>y>>z;
update(x,y,z,1,1,n);
}
cout<<"Case "<<i<<": The total value of the hook is "<<seg[1]<<".\n";
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
E 题
百炼oj 3243:A Simple Problem with Integers
题意:
让你实现区间修改数组,区间查询数组
思路:
线段树区间修改区间查询,不多说了
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
*/
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
ll n,q,seg[maxm<<2],tag[maxm<<2],a[maxm];
int ls(int p){return p<<1;}
int rs(int p){return p<<1|1;}
void push_up(int p){//更新seg
seg[p]=seg[ls(p)]+seg[rs(p)];
return ;
}
void build(int p,int pl,int pr){//建树
tag[p]=0;
if(pl==pr){
seg[p]=a[pl];
a[pl]=p;
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
void addtag(int p,int pl,int pr,ll k){//修改tag
tag[p]+=k;
seg[p]+=(pr-pl+1)*k;//已经对seg的值进行了修改
return ;
}
void push_down(int p,int pl,int pr){//下传tag
if(tag[p]!=0){
int mid=(pl+pr)>>1;
addtag(ls(p),pl,mid,tag[p]);
addtag(rs(p),mid+1,pr,tag[p]);
tag[p]=0;
}
return ;
}
void update(int l,int r,ll k,int p,int pl,int pr){//区间修改
if(l<=pl&&pr<=r){
addtag(p,pl,pr,k);
return ;
}
push_down(p,pl,pr);
int mid=(pl+pr)>>1;
if(l<=mid) update(l,r,k,ls(p),pl,mid);
if(mid<r) update(l,r,k,rs(p),mid+1,pr);
push_up(p);
return ;
}
ll query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r) return seg[p];
push_down(p,pl,pr);
ll res=0;
int mid=(pl+pr)>>1;
if(l<=mid) res+=query(l,r,ls(p),pl,mid);
if(mid<r) res+=query(l,r,rs(p),mid+1,pr);
return res;
}
void solve(){
cin>>n>>q;
for(int i=1;i<=n;++i) cin>>a[i];
build(1,1,n);
string ss;
ll x,y,c;
ll ans;
while(q--){
cin>>ss>>x>>y;
if(ss=="Q"){
ans=query(x,y,1,1,n);
cout<<ans<<'\n';
}else{
cin>>c;
update(x,y,c,1,1,n);
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
F 题
hdu 4027 Can you answer these queries?
题意:
给你 n 个邪恶的战舰排成一排,每一个战舰有一个对应的邪恶值。我们的指挥官决定使用我们的秘密武器来消灭战列舰。对于我们的秘密武器的每一次攻击,它都可以降低连续部分战列舰的续航力,使它们的耐力达到其原始续航力值的平方根。而指挥官想要知道,在多次使用时候,一些连续部分的战舰的邪恶值之和
思路:
You can assume that the sum of all endurance value is less than \(2^{63}\).
先考虑一件事情,就是,开多少次平方,\(2^{63}\)次方也会被开下来?7次
所以这题,单调修改就行,因为改着改着该变成1的早就变成1了。
所以,本题为单点修改+优化,区间查询
优化:update里面添加一句if(seg[p]<=pr-pl+1) return ;
因为这个式子说明,区间[pl,pr]内都是1,或者0(有的话),其内部所有都不用再计算了
当然,区间修改,统计tag,修改后标记一下,应该也行
另外就是输入战舰的序号的时候,x和y可能大小关系未知?(没看出来ing~)故需要排序一下
还是值得做的
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
*/
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
ll n,a[maxm],m,seg[maxm<<2];
int ls(int p){return p<<1;}
int rs(int p){return p<<1|1;}
void push_up(int p){//更新seg
seg[p]=seg[ls(p)]+seg[rs(p)];
return ;
}
void build(int p,int pl,int pr){//建树
if(pl==pr){
seg[p]=a[pl];
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
void update(int l,int r,int p,int pl,int pr){//单点修改
if(seg[p]<=pr-pl+1) return ;
if(pl==pr){
seg[p]=sqrt(seg[p]);
return ;
}
int mid=(pl+pr)>>1;
if(l<=mid) update(l,r,ls(p),pl,mid);
if(mid<r) update(l,r,rs(p),mid+1,pr);
push_up(p);
return ;
}
ll query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r){
return seg[p];
}
int mid=(pl+pr)>>1;
ll res=0;
if(l<=mid) res+=query(l,r,ls(p),pl,mid);
if(mid<r) res+=query(l,r,rs(p),mid+1,pr);
return res;
}
void solve(){
int cs=1;
while(cin>>n){
cout<<"Case #"<<cs<<":\n";
for(int i=1;i<=n;++i) cin>>a[i];
build(1,1,n);
cin>>m;
ll c,x,y;
while(m--){
cin>>c>>x>>y;
if(x>y) swap(x,y);
if(c==0){//update
update(x,y,1,1,n);
}else{//query
ll ans=query(x,y,1,1,n);
cout<<ans<<'\n';
}
}
cout<<'\n';
++cs;
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
G 题 未出,题解补
cf 474 E. Pillars
题意:
给你n个高度 h (顺序固定)和一个数 d ,要求你在其中找一个子序列,满足对于该子序列,相邻元素高度差的绝对值大于等于d,你需要找到最长的这样的子序列
思路:
一下子就联想到最长递增子序列的相关做法,但其在此处不适用
DP就是统计每个高度前后的最优解加上当前位置取最大值即可,思路简单,但是会TLE
\(O(n^2)\) DP 超时,故想到利用线段树快速找到一个位置前的DP最大值,再更新当前位置继续遍历
因为题目给的数字较为巨大,故还得配上离散化的思想
利用线段树优化DP,值得一做
有空我想用树状数组来一发,不知是否可行 [ ]待完成记
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
前面的最优解不一定是后面的最优解
故应该取前后的最优解形成最优解
利用线段树优化DP
晕~
*/
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
ll n,d,a[maxm],b[maxm],seg[maxm<<2],dp[maxm];
int ls(int p){return p<<1;}
int rs(int p){return p<<1|1;}
void push_up(int p){
seg[p]=max(seg[ls(p)],seg[rs(p)]); return ;
}
void build(int p,int pl,int pr){
if(pl==pr){ seg[p]=0; return ; }
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
void update(int l,int r,ll k,int p,int pl,int pr){
if(pl==pr){ seg[p]=max(seg[p],k); return ; }
int mid=(pl+pr)>>1;
if(l<=mid) update(l,r,k,ls(p),pl,mid);
if(mid<r) update(l,r,k,rs(p),mid+1,pr);
push_up(p);
return ;
}
ll query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r) return seg[p];
ll ans=0;
int mid=(pl+pr)>>1;
if(l<=mid) ans=max(ans,query(l,r,ls(p),pl,mid));
if(mid<r) ans=max(ans,query(l,r,rs(p),mid+1,pr));
return ans;
}
void print(int x,int v,ll p){
for(int i=x;i>=1;--i){
if(dp[i]==v && (a[i]>=p+d || a[i]<=p-d)){
print(i,v-1,a[i]);
cout<<i<<' ';
break;
}
}
return ;
}
void solve(){
cin>>n>>d;
for(int i=1;i<=n;++i){
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+1+n);//用于离散化到1~n
build(1,1,n);
ll ans=0;
int pp;
for(int i=1;i<=n;++i){
//二分查找两边区间[~,<=a[i]-d]与[>=a[i]+d,~]的最大值
int id1=lower_bound(b+1,b+1+n,a[i]+d)-b;
int id2=upper_bound(b+1,b+1+n,a[i]-d)-b-1;//-1为了前挪一个位置
int pos=lower_bound(b+1,b+1+n,a[i])-b;
if(id2>=1 && b[id2]<=a[i]-d)
dp[i]=max(dp[i],query(1,id2,1,1,n));
if(id1<=n && b[id1]>=a[i]+d)
dp[i]=max(dp[i],query(id1,n,1,1,n));
++dp[i];//加上本身
update(pos,pos,dp[i],1,1,n);//单点修改,利用线段树找dpmax
if(dp[i]>ans){//dp[i]表示到i位置的最优解
ans=dp[i];
pp=i;
}
}
cout<<ans<<'\n';
print(pp-1,ans-1,a[pp]);//递归输出答案
cout<<pp<<'\n';
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
相关资料;
https://blog.csdn.net/qq_37451344/article/details/82904204
H 题 未出,题解补
洛谷 P4513 小白逛公园
题意:
给你n个依次排列的公园,每个公园有一个分数,后面会执行两个操作:一是修改某个公园的分数,二是给你一个游玩公园的位置区间,问你去游玩这个区间里面连续的公园的得分和的最大值
思路:
线段树单点修改+不一样的区间查询
单点修改不必多说,这里就说说怎么找连续子区间的最大和
连续子区间?是否可以联想到分治法求解最大连续子序列?其实这也同理
我们对每一个p管辖的区间都设置几个新的参数:ans,sum,maxl,maxr
ans: 当前 p 区间里的最大连续和
sum: p 区间的和
maxl: 包含 p 区间左端点的最大连续和
maxr: 包含 p 区间右端点的最大连续和
之后对于 p 区间的左右两个子区间,我们可以得到以下的push_up()函数:
void push_up(int p){
seg[p].sum=seg[ls(p)].sum+seg[rs(p)].sum;
seg[p].maxl=max(seg[ls(p)].maxl,seg[ls(p)].sum+seg[rs(p)].maxl);
seg[p].maxr=max(seg[rs(p)].maxr,seg[rs(p)].sum+seg[ls(p)].maxr);
seg[p].ans=max(seg[ls(p)].maxr+seg[rs(p)].maxl,max(seg[ls(p)].ans,seg[rs(p)].ans));
return ;
}
同理于区间查询,我们也可以对于以类似于上面的push_up()操作实现新区间的查询
node query(int p,int l,int r){
/*
简单的返回sum或ans都是无法满足最优这个条件的
那么只能返回node了
再统计区间的最优解!
*/
int pl=seg[p].l,pr=seg[p].r;
if(l<=pl&&pr<=r){
return seg[p];
}
int mid=(pl+pr)>>1;
if(r<=mid) return query(ls(p),l,r);//查询区间全左
else if(l>mid) return query(rs(p),l,r);//查询区间全右
else{//查询区间左右均有,需合并
node t,x,y;
x=query(ls(p),l,r);
y=query(rs(p),l,r);
t.sum=x.sum+y.sum;
t.maxl=max(x.maxl,x.sum+y.maxl);
t.maxr=max(y.maxr,y.sum+x.maxr);
t.ans=max(max(x.ans,y.ans),x.maxr+y.maxl);
return t;
}
}
至此,此题目的最大问题,如何寻找已经完成,至于修改问题即为简单,单点修改和push_up更新即可
具体代码如下:
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
单点修改,区间查询连续子区间最大值
*/
const int maxm=5e5+5,inf=0x3f3f3f3f,mod=998244353;
int n,m,a[maxm];
struct node{
int l,r,maxl,maxr,sum,ans;
}seg[maxm<<4];
int ls(int p){ return p<<1; }
int rs(int p){ return p<<1|1; }
void push_up(int p){
seg[p].sum=seg[ls(p)].sum+seg[rs(p)].sum;
seg[p].maxl=max(seg[ls(p)].maxl,seg[ls(p)].sum+seg[rs(p)].maxl);
seg[p].maxr=max(seg[rs(p)].maxr,seg[rs(p)].sum+seg[ls(p)].maxr);
seg[p].ans=max(seg[ls(p)].maxr+seg[rs(p)].maxl,max(seg[ls(p)].ans,seg[rs(p)].ans));
return ;
}
void build(int p,int pl,int pr){
seg[p].l=pl;
seg[p].r=pr;
if(pl==pr){
seg[p].ans=seg[p].maxl=seg[p].maxr=seg[p].sum=a[pl];
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
void update(int p,int x,int k){
int pl=seg[p].l,pr=seg[p].r;
if(pl==pr){
seg[p].ans=seg[p].maxl=seg[p].maxr=seg[p].sum=k;
return ;
}
int mid=(pl+pr)>>1;
if(x<=mid) update(ls(p),x,k);
else update(rs(p),x,k);
push_up(p);
return ;
}
node query(int p,int l,int r){
/*
简单的返回sum或ans都是无法满足最优这个条件的
那么只能返回node了
再统计区间的最优解!
*/
int pl=seg[p].l,pr=seg[p].r;
if(l<=pl&&pr<=r){
return seg[p];
}
int mid=(pl+pr)>>1;
if(r<=mid) return query(ls(p),l,r);
else if(l>mid) return query(rs(p),l,r);
else{
node t,x,y;
x=query(ls(p),l,r);
y=query(rs(p),l,r);
t.sum=x.sum+y.sum;
t.maxl=max(x.maxl,x.sum+y.maxl);
t.maxr=max(y.maxr,y.sum+x.maxr);
t.ans=max(max(x.ans,y.ans),x.maxr+y.maxl);
return t;
}
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
int c,x,y;
node t;
while(m--){
cin>>c>>x>>y;
if(c==1){//query
if(x>y) swap(x,y);
t=query(1,x,y);
cout<<t.ans<<'\n';
}else{//update
update(1,x,y);
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
相关资料:
https://www.luogu.com.cn/blog/lc-2018-Canton/solution-p4513
I 题 未出 题解补
洛谷 P7492 [传智杯 #3 决赛] 序列
题意:
你有一个长为 n 的序列 a ,现在要对其进行 m 次操作。每次操作可以选择询问指定区间[l,r]的最大连续子段和,或者对区间[l,r]内的\(a_i\)均位或上一个k
思路:
整体代码与H题相同,故可以参考H题的代码,这里给出本题不同的地方
本题利用一个tag标记表示p所在区间的元素全部位与后的结果,利用这个可以标识,对于一个k,如果说 \(tag[p] | k=tag[p]\),那么这个k位或了之后不会产生任何影响,所以这个k就不需要进行update了。
另外就是文中标注的max问题,我没有很清楚的发现,连续字段和可以不取?很疑惑。。。所以得和 0 取大,或者说那里不取大,最终结果取大也行。
下为代码:
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
*/
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
ll n,m,a[maxm],tag[maxm<<2];
int ls(int p) { return p<<1; }
int rs(int p) { return p<<1|1; }
struct node{
ll ans,sum,maxl,maxr;
}seg[maxm<<2];
void push_up(int p){
seg[p].sum=seg[ls(p)].sum+seg[rs(p)].sum;
seg[p].maxl=max(seg[ls(p)].maxl,seg[ls(p)].sum+seg[rs(p)].maxl);
seg[p].maxr=max(seg[rs(p)].maxr,seg[rs(p)].sum+seg[ls(p)].maxr);
seg[p].ans=max(max(seg[ls(p)].ans,seg[rs(p)].ans),seg[ls(p)].maxr+seg[rs(p)].maxl);
tag[p]=tag[ls(p)]&tag[rs(p)];
return ;
}
void build(int p,int pl,int pr){
if(pl==pr){
tag[p]=a[pl];
seg[p].sum=a[pl];
seg[p].ans=max(0ll,a[pl]);//???取max???
seg[p].maxl=max(0ll,a[pl]);
seg[p].maxr=max(0ll,a[pl]);
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
void update(int l,int r,ll k,int p,int pl,int pr){
if((tag[p]|k)==tag[p]) return ;//剪枝
if(pl==pr){
tag[p]|=k;
seg[p].sum=tag[p];
seg[p].ans=max(0ll,tag[p]);
seg[p].maxl=max(0ll,tag[p]);
seg[p].maxr=max(0ll,tag[p]);
return ;
}
int mid=(pl+pr)>>1;
if(l<=mid) update(l,r,k,ls(p),pl,mid);
if(mid<r) update(l,r,k,rs(p),mid+1,pr);
push_up(p);
return ;
}
node query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r) return seg[p];
int mid=(pl+pr)>>1;
if(r<=mid) return query(l,r,ls(p),pl,mid);
if(l>mid) return query(l,r,rs(p),mid+1,pr);
node t,x=query(l,r,ls(p),pl,mid),y=query(l,r,rs(p),mid+1,pr);
t.sum=x.sum+y.sum;
t.maxl=max(x.maxl,x.sum+y.maxl);
t.maxr=max(y.maxr,y.sum+x.maxr);
t.ans=max(max(x.ans,y.ans),x.maxr+y.maxl);
return t;
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
ll op,x,y,k;
node t;
while(m--){
cin>>op>>x>>y;
if(op==1){//query
t=query(x,y,1,1,n);
cout<<t.ans<<'\n';
}else{//update
cin>>k;
update(x,y,k,1,1,n);
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
思路来自洛谷题解
J 题
cf 920 F. SUM and REPLACE
题意:
给你n个数,按照顺序排列,再进行m次操作。每次操作要么是问你区间[l,r]的和,要么是让你将区间[l,r]的所有数\(a_i=D(a_i),D(i)=i的因子数\),如:\(D(2)=2 (因子:1,2),D(6)=4 (因子:1,2,3,6)\)
思路:
与上面的F题有点像,其实可以很容易知道,在不超过10次左右的次数,数字就会变成1或2,所以单点修改,打上标记判断即可。但本题的特殊性在于,所有数的最后收敛到1或2,这如果简单的用区间和,其实不好判断。为此,我们牺牲空间,再增加一个线段树,一个标识区间和,为了query操作;一个标识区间最大值,为了update操作。当区间最大值为2时,整个p所代表的区间都已经达到稳定了,无需再改(因为\(D(2)=2,D(1)=1\))。
所以,使用两个线段树,在update中加一句if(mseg[p]<=2) return ;
即可
想到这个思路则非常简单!
下面放个求因子个数的方法
ll calc(ll x){//求因子个数
if(cnt.count(x)) return cnt[x];
ll ans=1;
for(ll i=2;i*i<=x;++i){
if(x%i==0){
ll c=0;
while(x%i==0){
++c;
x/=i;
}
ans*=c+1;
}
}
if(x!=1) ans*=2;
return ans;
}
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
与前面那道开方的题比较类似?
*/
const int maxm=3e5+5,inf=0x3f3f3f3f,mod=998244353;
ll n,m,a[maxm],seg[maxm<<2],mseg[maxm<<2];
map<ll,ll> cnt;
int ls(int p) { return p<<1; }
int rs(int p) { return p<<1|1; }
void push_up(int p){
seg[p]=seg[ls(p)]+seg[rs(p)];
mseg[p]=max(mseg[ls(p)],mseg[rs(p)]);
return ;
}
void build(int p,int pl,int pr){
if(pl==pr) {
mseg[p]=seg[p]=a[pl];
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
ll calc(ll x){//求因子个数
if(cnt.count(x)) return cnt[x];
ll ans=1;
for(ll i=2;i*i<=x;++i){
if(x%i==0){
ll c=0;
while(x%i==0){
++c;
x/=i;
}
ans*=c+1;
}
}
if(x!=1) ans*=2;
return ans;
}
void update(int l,int r,int p,int pl,int pr){
// if(pr-pl+1>=seg[p]) return ;这句话写假了,因为D(2)=2!!!
if(mseg[p]<=2) return ;
if(pl==pr){
mseg[p]=seg[p]=calc(seg[p]);
return ;
}
int mid=(pl+pr)>>1;
if(l<=mid) update(l,r,ls(p),pl,mid);
if(mid<r) update(l,r,rs(p),mid+1,pr);
push_up(p);
return ;
}
ll query(int l,int r,int p,int pl,int pr){
if(l<=pl && pr<=r) return seg[p];
int mid=(pl+pr)>>1;
ll ans=0;
if(l<=mid) ans+=query(l,r,ls(p),pl,mid);
if(mid<r) ans+=query(l,r,rs(p),mid+1,pr);
return ans;
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
ll c,x,y,ans;
while(m--){
cin>>c>>x>>y;
if(c==1){//update
update(x,y,1,1,n);
}else{//query
cout<<query(x,y,1,1,n)<<'\n';
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
K 题
cf 438 D. The Child and Sequence
题意:
给你长度为n的数组,共进行m次操作。每次操作要么求给定区间[l,r]的和;要么对于区间[l,r],对该区间内所有的i,均进行\(a[i]=a[i]\%x\)的修改;要么令\(a[k]=x\)。
思路:
与J题类似,依旧是双线段树,一个维护区间和,为了query区间和;一个维护区间最大值,为了在进行操作2时,剪枝,就是当所在区间的最大值小于x时,就不需要进行取余操作了
另外就是标记一点,不出意外的话,本题直接利用tag标记取余可能不太行嗷,因为假设三个数1,2,5,对于3取余后求和,和为5,但是利用tag,对于和取余,结果为2。所以不太行。(这个是自己一开始的思路,后面发现错了)
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
双线段树维护
*/
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
ll n,m,a[maxm],seg[maxm<<2],mseg[maxm<<2];
int ls(int p){ return p<<1; }
int rs(int p){ return p<<1|1; }
void push_up(int p){
seg[p]=seg[ls(p)]+seg[rs(p)];
mseg[p]=max(mseg[ls(p)],mseg[rs(p)]);
return ;
}
void build(int p,int pl,int pr){
if(pl==pr){
mseg[p]=seg[p]=a[pl];
a[pl]=p;
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
void update(int pos,ll k,int p,int pl,int pr){
if(pl==pr){
mseg[p]=seg[p]=k;
return ;
}
int mid=(pl+pr)>>1;
if(pos<=mid) update(pos,k,ls(p),pl,mid);
else update(pos,k,rs(p),mid+1,pr);
push_up(p);
return ;
}
void modd(int l,int r,ll k,int p,int pl,int pr){
if(mseg[p]<k) return ;
if(pl==pr){
seg[p]%=k;
mseg[p]=seg[p];
return ;
}
int mid=(pl+pr)>>1;
if(l<=mid) modd(l,r,k,ls(p),pl,mid);
if(mid<r) modd(l,r,k,rs(p),mid+1,pr);
push_up(p);
return ;
}
ll query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r) return seg[p];
ll ans=0;
int mid=(pl+pr)>>1;
if(l<=mid) ans+=query(l,r,ls(p),pl,mid);
if(mid<r) ans+=query(l,r,rs(p),mid+1,pr);
return ans;
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
ll c,x,y,k;
while(m--){
cin>>c>>x>>y;
if(c==1){//query
cout<<query(x,y,1,1,n)<<'\n';
}else if(c==2){//mod
cin>>k;
modd(x,y,k,1,1,n);
}else{//update a[k]=x;
update(x,y,1,1,n);
}
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
L 题
cf 1187 D. Subarray Sorting
题意:
给你两个长度均为n的数组a和数组b,你可以对数组a执行任意次指定的操作——对a的某一个区间排序成非降序顺序。问你,能否通过操作,使得a数组变为b数组?
思路:
尽管是线段树专题,但乍一眼,看不到与线段树有任何的关系。。。那就得分析题目的意思,之后判断怎么用线段树
首先,a数组和b数组的所包含的元素种类和个数必须相同,不然无可能
其次,对于b中一个元素,都是由a中通过操作得到的。如果说a与b对应位置已经相等,那么这个位置就不用管了,因为已经符合条件了,或者说这个位置就可以不用考虑了。
那么我们从头开始看b数组,若相等,则删去当前位置的影响(怎么删等会说),考虑下一个位置;若不等,则我们需要找到a中第一个和它相等的位置pos(为啥不是最后一个或者别的?...题目问的是成立性!第一个最有可能成立,此处不多赘述),再移过来。因为我们是从前往后找的,前面的都是符合条件了的,这就相当于将其新的符合要求的数挪到a数组的开头,这个开头可以看作,一个动态的数组a的开头已经符合条件的数都已经被删去了。而能否移到开头?通过题目的定义我们可以知道,最小的数才能挪到开头,所以我们利用线段树维护区间最小值即可。那么已经挪过的数怎么取消影响呢?将其线段树内单点修改为极大值即可,这样就不会影响了。
这题主要是思路,有了做题的思路,那么就简单了!
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
排序肯定不是关键,主要是判断,b数组中的数能不能由a数组中的数移过来
线段树维护区间最小值,想法想假了,仅判断最小值不够用
*/
const int maxm=3e5+5,inf=0x3f3f3f3f,mod=998244353;
int n,a[maxm],seg[maxm<<2],b[maxm];
deque<int> q[maxm];
int ls(int p){return p<<1;}
int rs(int p){return p<<1|1;}
void push_up(int p){//更新seg
seg[p]=min(seg[ls(p)],seg[rs(p)]);
return ;
}
void build(int p,int pl,int pr){//建树
if(pl==pr){
seg[p]=a[pl];
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
void update(int pos,ll k,int p,int pl,int pr){
if(pl==pr){
seg[p]=k;
return ;
}
int mid=(pl+pr)>>1;
if(pos<=mid) update(pos,k,ls(p),pl,mid);
else update(pos,k,rs(p),mid+1,pr);
push_up(p);
return ;
}
ll query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r) return seg[p];
int mid=(pl+pr)>>1;
ll res=inf;
if(l<=mid) res=min(res,query(l,r,ls(p),pl,mid));
if(mid<r) res=min(res,query(l,r,rs(p),mid+1,pr));
return res;
}
void solve(){
cin>>n;
for(int i=1;i<=n;++i){
q[i].clear();
}
for(int i=1;i<=n;++i){
cin>>a[i];
q[a[i]].push_back(i);
}
build(1,1,n);
bool f=true;
int t,pos,dm;
for(int i=1;i<=n;++i){
cin>>b[i];
}
for(int i=1;i<=n;++i){
t=b[i];
if(!f) continue;
if(q[t].size()){
pos=q[t].front();
q[t].pop_front();
dm=query(1,pos,1,1,n);
if(dm<t){
f=false;
continue;
}
update(pos,inf,1,1,n);
}else{
f=false;
}
}
if(f) cout<<"YES\n";
else cout<<"NO\n";
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
cin>>_;
while(_--){
solve();
}
return 0;
}
M 题
cf 380 C. Sereja and Brackets
题意:
给你一个括号序列,再进行m次询问,每次询问一个区间[l,r]内的括号匹配的最大数量
思路:
参考本博客的H题和I题,思路大体一致。
怎么说呢这题,其实,答案在给定括号序列的时候已经确定了,我们要做的就是如何确定答案。
我们先考虑括号与括号间的关系:"()","((","))",")(",四种情况第一种匹配成功,二三种同左同右,第四种左右都有但是顺序不对。那么,括号数再多一点呢?
以"(())))(())..."与")))(()))))..."这俩为例,他俩合并后,整个序列的匹配数是不是等于左边的匹配数+右边的匹配数+min(左边左括号未匹配数,右边右括号未匹配数)?,整个序列的未匹配括号数就是剩下的括号数。既然如此,那么我们是不是可以从每个字符出发构造线段树?为每个区间设置3个参数:ans,ml,mr,分别标识匹配数,左未匹配数,右未匹配数。
那么!线段树的建立过程就很明显了,仅修改push_up()函数即可
void push_up(int p){
int d=min(seg[ls(p)].ml,seg[rs(p)].mr);
seg[p].ans=seg[ls(p)].ans+seg[rs(p)].ans+d*2;
seg[p].ml=seg[ls(p)].ml+seg[rs(p)].ml-d;
seg[p].mr=seg[ls(p)].mr+seg[rs(p)].mr-d;
return ;
}
查询操作与之对应!返回node,答案即为node x.ans
下为代码:
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
与之前做的一道询问区间的查询连续子区间最大和的题目类似
*/
const int maxm=1e6+5,inf=0x3f3f3f3f,mod=998244353;
string ss;
int n,m;
struct node{
int ans,ml,mr;
}seg[maxm<<2];
int ls(int p) {return p<<1;}
int rs(int p) {return p<<1|1;}
void push_up(int p){
int d=min(seg[ls(p)].ml,seg[rs(p)].mr);
seg[p].ans=seg[ls(p)].ans+seg[rs(p)].ans+d*2;
seg[p].ml=seg[ls(p)].ml+seg[rs(p)].ml-d;
seg[p].mr=seg[ls(p)].mr+seg[rs(p)].mr-d;
return ;
}
void build(int p,int pl,int pr){
if(pl==pr){
seg[p].ans=seg[p].ml=seg[p].mr=0;
if(ss[pl-1]=='(') seg[p].ml=1;
else seg[p].mr=1;
return ;
}
int mid=(pl+pr)>>1;
build(ls(p),pl,mid);
build(rs(p),mid+1,pr);
push_up(p);
return ;
}
node query(int l,int r,int p,int pl,int pr){
if(l<=pl&&pr<=r) return seg[p];
int mid=(pl+pr)>>1;
if(r<=mid) return query(l,r,ls(p),pl,mid);
else if(l>mid) return query(l,r,rs(p),mid+1,pr);
else{
node t,x=query(l,r,ls(p),pl,mid),y=query(l,r,rs(p),mid+1,pr);
int d=min(x.ml,y.mr);
t.ans=x.ans+y.ans+d*2;
t.ml=x.ml+y.ml-d;
t.mr=x.mr+y.mr-d;
return t;
}
}
void solve(){
cin>>ss;
n=ss.size();
build(1,1,n);
cin>>m;
int l,r;
while(m--){
cin>>l>>r;
cout<<query(l,r,1,1,n).ans<<'\n';
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
题
题意:
思路: