线段树暑假进阶
线段树
数学计算
这道题难在建模
线段树维护每次乘的数,然后除法就把当时乘的数变成\(1\) 复杂度 \(O(QlogQ)\)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=1000005;
inline int read() {
int 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*10+ch-'0';ch=getchar();}
return f*x;
}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
int n,mod,rt,cnt;
int ls[N],rs[N];
ll sum[N];
#define mid ((l+r)>>1)
void build(int l,int r,int &p) {
p=++cnt;
if(l==r) { sum[p]=1;return; }
build(l,mid,ls[p]);
build(mid+1,r,rs[p]);
sum[p]=(sum[ls[p]]*sum[rs[p]])%mod;
}
void modify(int l,int r,int pos,int v,int &p) {
if(l==r) { sum[p]=v;return; }
if(pos<=mid) modify(l,mid,pos,v,ls[p]);
else modify(mid+1,r,pos,v,rs[p]);
sum[p]=(sum[ls[p]]*sum[rs[p]])%mod;
}
ll query(int l,int r,int L,int R,int p) {
if(L<=l&&r<=R) return sum[p];
ll res=1;
if(L<=mid) res=res*query(l,mid,L,R,ls[p])%mod;
if(R>mid) res=res*query(mid+1,r,L,R,rs[p])%mod;
return res;
}
int main() {
int T=read();
while(T--) {
cnt=0;
memset(sum,0,sizeof(sum));
memset(ls,0,sizeof(ls));
memset(rs,0,sizeof(rs));
n=read();mod=read();
build(1,n,rt);
for(int i=1,op,pos;i<=n;i++) {
op=read();pos=read();
if(op==1) {
modify(1,n,i,pos,rt);
printf("%lld\n",query(1,n,1,n,rt));
}
else {
modify(1,n,pos,1,rt);
printf("%lld\n",query(1,n,1,n,rt));
}
}
}
return 0;
}
NOI2016 区间
Description
从\(n\)个区间内选\(m\)个区间,使得m个区间共同包含一个点,花费为所选最长区间长 — 所选最短区间长度
Solution
首先我们考虑暴力怎么做,按长度排序之后,我们容易发现,如果枚举一个区间作为左端点,一个区间作为右端点,那么我们就是求只在这个区间中选取的答案。
我们把这一段的所有区间的对应的一段的经过次数都加一(区间加),最后只需\(𝑐ℎ𝑒𝑐𝑘\)一下这一段中是否出现了一个被经过\(𝑚\)次的点(区间查询),一旦存在就说明,我们一定可以找到其中的\(𝑚\)个区间满足题目的要求,所以我们就可以确保在这个区间中能够选取\(𝑚\)个区间并一定合法,就可以用右端点的那个区间长度 — 左端点的那个区间长度来更新答案。(并不关心具体选了哪些区间,中间有多余的也不影响答案)
上述做法的复杂度可以用线段树维护来做到\(O(𝑛^2𝑙𝑜𝑔𝑛)\)。深入思考可以发现,其实右端点肯定是不降的,所以我们没必要再枚举一个右端点,只要用单调指针一直往后扫即可。总复杂度为:\(𝑂(𝑛𝑙𝑜𝑔𝑛)\)
血与泪的教训——没事别加那么多 inline ——MLE
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=5000010;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,m;
struct section{
int l,r,len;
bool operator < (const section &x)const{
return len<x.len;
}
}a[N*4];
int tot,b[N];
// 维护最大值
int tree_cnt,val[N],tag[N],ls[N],rs[N],root;
void pushup(int p){
val[p]=max(val[ls[p]],val[rs[p]]);
}
void pushdown(int p){
if(!tag[p]) return;
val[ls[p]]+=tag[p];
val[rs[p]]+=tag[p];
tag[ls[p]]+=tag[p];
tag[rs[p]]+=tag[p];
tag[p]=0;
}
void build(int l,int r,int &p){
p=++tree_cnt;
if(l==r) return;
int mid=(l+r)>>1;
build(l,mid,ls[p]);
build(mid+1,r,rs[p]);
}
void modify(int L,int R,int l,int r,int add,int p){
if(L<=l&&r<=R){tag[p]+=add;val[p]+=add;return;}
pushdown(p);
int mid=(l+r)>>1;
if(L<=mid) modify(L,R,l,mid,add,ls[p]);
if(R>mid) modify(L,R,mid+1,r,add,rs[p]);
pushup(p);
}
int ans=0x3f3f3f3f;
int main(){
n=read();m=read();
for(int i=1;i<=n;i++){
b[++tot]=a[i].l=read();b[++tot]=a[i].r=read();
a[i].len=a[i].r-a[i].l+1;
}
sort(b+1,b+1+tot);
tot=unique(b+1,b+1+tot)-b-1;
for(int i=1;i<=n;i++){
a[i].l=lower_bound(b+1,b+1+tot,a[i].l)-b;
a[i].r=lower_bound(b+1,b+1+tot,a[i].r)-b;
}
sort(a+1,a+1+n);
build(1,tot,root);
int h=1,t=0;
while(val[root]<m&&++t<=n)
modify(a[t].l,a[t].r,1,tot,1,root);
if(t==n+1) {
puts("-1");return 0;
}
ans=min(ans,a[t].len-a[h].len);
while(h<=n){
modify(a[h].l,a[h].r,1,tot,-1,root);
while(val[root]<m&&++t<=n)
modify(a[t].l,a[t].r,1,tot,1,root);
if(t==n+1) break;
ans=min(ans,a[t].len-a[h+1].len);//注意 h+1
h++;
}
printf("%d\n",ans);
return 0;
}
HEOI 2012 采花
3e5的范围莫队水过
类比HH的项链,离线下来,按\(r\)排序,我们维护一个\(vis[i]\)表示\(i\)上次出现的位置,然后树状数组维护,每次\(upd(j,1),upd(vis[j],-1)\),然后计算再求和
这道题目要求只存在两个相同颜色,怎么办呢?
我们维护两个东西,\(last1[j]\)表示上上次出现j的位置,\(last2[j]\)表示上次出现的位置
对于第\(p\)个的数 \(j\)
第一次出现 \(j\) 时没有用,我们直接记录\(last1=p\);
第二次出现\(j\)时,他就会产生代价,但值得注意的是我们不应该在第二次出现\(j\)的位置上\(+1\),而是在上一次出现的位置\(last1\)上\(+1\),这是因为我们按照\(r\)从小到大排序,比如\(2,2,3\)这个序列,如果我们在第二次出现\(2\)的位置上\(+1\),变成\((0,1,0)\),当询问\([2,3]\)就不对了,应该在倒数第二次出现的位置\(+1\)变成\((1,0,0)\);
两次以上出现,我们只需要在倒数第\(2\)次的位置上\(+1\),其他位置上出现\(j\)全部为零,实现:\(add(last1[j],-1);add(last2[j],1),last1=last2,last2=j;\)
#include <cstdio>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=2e6+10;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
int n,m,num,c[N],bel[N];
struct node{
int l,r,id;
bool operator < (const node &x) const {
return r<x.r;
}
}q[N];
int t[N<<1];
inline void upd(int x,int v){for(;x<=n;x+=x&(-x))t[x]+=v;}
inline int sum(int x){int res=0;for(;x;x-=x&(-x))res+=t[x];return res;}
int ans[N],res;
int pre1[N],pre2[N];
int main() {
n=read();num=read();m=read();
for(int i=1;i<=n;i++) c[i]=read();
for(int i=1;i<=m;i++)
q[i].l=read(),q[i].r=read(),q[i].id=i;
sort(q+1,q+1+m);
int nxt=1;
for(int i=1;i<=m;i++) {
for(int j=nxt;j<=q[i].r;j++) {
if(pre2[c[j]]) {
upd(pre1[c[j]],-1);
upd(pre2[c[j]],1);
pre1[c[j]]=pre2[c[j]];pre2[c[j]]=j;
}
else if(pre1[c[j]]&&!pre2[c[j]]) {
upd(pre1[c[j]],1);
pre2[c[j]]=j;
}
else if(!pre1[c[j]]) {
pre1[c[j]]=j;
}
}
nxt=q[i].r+1;
ans[q[i].id]=sum(q[i].r)-sum(q[i].l-1);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}
card
线段树维护图的联通性
不妨假设\(a_i<b_i\)(不满足交换),线段树维护区间左端点取\(a_i/b_i\),右端点最小是多少,修改时相当于两次单点修改(注意修改是永久的)
#include<bits/stdc++.h>
using namespace std;
#define N 200005
#define oo 0x3f3f3f3f
#define L (p<<1)
#define R (L+1)
#define mid (l+r>>1)
int n,m,x,y,a[N][2],f[N<<2][2];
void update(int p,int l,int r,int x){
if (l==r){
f[p][0]=a[l][0];
f[p][1]=a[r][1];
return;
}
if (x<=mid)update(L,l,mid,x);
else update(R,mid+1,r,x);
f[p][0]=f[p][1]=oo;
for(int i=0;i<2;i++)
for(int j=1;j>=0;j--)
if (f[L][i]<=a[mid+1][j])f[p][i]=f[R][j];
}
int main(){
scanf("%d",&n);
memset(f,oo,sizeof(f));
a[n+1][0]=a[n+1][1]=oo-1;
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i][0],&a[i][1]);
if (a[i][0]>a[i][1])swap(a[i][0],a[i][1]);
update(1,1,n,i);
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
swap(a[x][0],a[y][0]);
swap(a[x][1],a[y][1]);
update(1,1,n,x);
update(1,1,n,y);
if (f[1][0]==oo)printf("NIE\n");
else printf("TAK\n");
}
}
SHOI2008 堵塞的交通——wait
6种情况 以每一列为一个叶子节点。
左上右下、左上右上、左下右下、左下右上、左上左下,右上右下
修改横向相邻的道路,所以我们还要维护一个side[]表示这个节点是否可以向外延伸。
https://www.cnblogs.com/SiriusRen/p/6536953.html
一脸不可做——下面是std——有空再说
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100100;
struct S{bool luru,ldrd,lurd,ldru,luld,rurd,side[3];}tr[N*4];
char ch[10];
int n,r1,c1,r2,c2;
#define mid (l+r)/2
#define L k<<1,l,mid
#define R k<<1|1,mid+1,r
S update(S x,S y)
{
S ans;
ans.side[1]=y.side[1];
ans.side[2]=y.side[2];
ans.luru=ans.ldrd=ans.lurd=ans.ldru=ans.luld=ans.rurd=false;
if((x.luru&&x.side[1]&&y.luru)||(x.lurd&&x.side[2]&&y.ldru)) ans.luru=true;
if((x.ldrd&&x.side[2]&&y.ldrd)||(x.ldru&&x.side[1]&&y.lurd)) ans.ldrd=true;
if((x.luru&&x.side[1]&&y.lurd)||(x.lurd&&x.side[2]&&y.ldrd)) ans.lurd=true;
if((x.ldrd&&x.side[2]&&y.ldru)||(x.ldru&&x.side[1]&&y.luru)) ans.ldru=true;
if((x.luld)||(x.luru&&x.side[1]&&y.luld&&x.side[2]&&x.ldrd)) ans.luld=true;
if((y.rurd)||(y.luru&&x.side[1]&&x.rurd&&x.side[2]&&y.ldrd)) ans.rurd=true;
return ans;
}
void build(int k,int l,int r)
{
if(l==r){
tr[k].luru=tr[k].ldrd=true;
return ;
}
build(L);build(R);
tr[k]=update(tr[k<<1],tr[k<<1|1]);
}
void insert_a(int k,int l,int r,int x,bool kind)
{
if(l==r){
tr[k].luld=tr[k].rurd=tr[k].lurd=tr[k].ldru=kind;
return ;
}
if(x<=mid) insert_a(L,x,kind);
else insert_a(R,x,kind);
tr[k]=update(tr[k<<1],tr[k<<1|1]);
}
void insert_b(int k,int l,int r,int x,int y,bool kind)
{
if(l==r){
tr[k].side[y]=kind;
return ;
}
if(x<=mid) insert_b(L,x,y,kind);
else insert_b(R,x,y,kind);
tr[k]=update(tr[k<<1],tr[k<<1|1]);
}
S query(int k,int l,int r,int x,int y)
{
S ans1,ans2;
bool left=false,right=false;
if(x<=l&&y>=r) return tr[k];
if(x<=mid) ans1=query(L,x,y),left=true;
if(y>mid) ans2=query(R,x,y),right=true;
if(right&&left) return update(ans1,ans2);
else return left?ans1:ans2;
}
bool check()
{
S now,pre,last;
if(c1>c2){
swap(r1,r2);
swap(c1,c2);
}
now=query(1,1,n,c1,c2);
pre=query(1,1,n,1,c1);
last=query(1,1,n,c2,n);
if(r1==r2){
if((r1==1)&&((now.luru)||(last.luld&&now.lurd)||(pre.rurd&&now.ldru)||(pre.rurd&&now.ldrd&&last.luld))) return true;
if((r1==2)&&((now.ldrd)||(last.luld&&now.ldru)||(pre.rurd&&now.lurd)||(pre.rurd&&now.luru&&last.luld))) return true;
}
else{
if((r1==1)&&((now.lurd)||(last.luld&&now.luru)||(pre.rurd&&now.ldrd)||(pre.rurd&&now.ldru&&last.luld))) return true;
if((r1==2)&&((now.ldru)||(last.luld&&now.ldrd)||(pre.rurd&&now.luru)||(pre.rurd&&now.lurd&&last.luld))) return true;
}
return false;
}
int main()
{
int i,j;
scanf("%d",&n);
build(1,1,n);
while(scanf("%*c%s",&ch)){
if(ch[0]=='E') break;
scanf("%d%d%d%d",&r1,&c1,&r2,&c2);
if(ch[0]=='O'){
if(c1==c2) insert_a(1,1,n,c1,1);
else insert_b(1,1,n,min(c1,c2),r1,1);
}
if(ch[0]=='C'){
if(c1==c2) insert_a(1,1,n,c1,0);
else insert_b(1,1,n,min(c1,c2),r1,0);
}
if(ch[0]=='A'){
if(check()) printf("Y\n");
else printf("N\n");
}
}
}
大都市MEG-Megalopolis
https://www.luogu.com.cn/problem/P3459
线段树上二分
还有一道考试题——https://www.cnblogs.com/ke-xin/p/13843379.html
按照生长速度a[]排序后,容易发现数列永远单调不降。
也就是说,每次砍完生长速度快的可能等于生长速度慢的,但之后立马又长得比他高。
所以每次操作都是一段连续的区间,可以线段树维护
1.最后一棵草的高度a
2.上次收割日期b
3.总的高度和c
4.总的生长速度和d
5.高度覆盖标记和时间标记
对于一次操作$$ x_i,y_i$$ 在线段树上二分找到第一个大于等于$$ x_i$$的位置$$ pos$$,然后将 $$[pos,n]$$全部覆盖为$$ y_i$$,顺便在覆盖的同时计算改变量即为答案。
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <utility>
#include <algorithm>
using namespace std;
// typedef long long int;
#define int long long
inline int read() {
int 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*10+ch-'0';ch=getchar();}
return x*f;
}
const int N=1e6+10;
const int inf=1e9;
int n,m,a[N];
int sum[N];
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
struct node
{
int tim,mx,sum,tag;
}t[N*4];
void build(int l,int r,int p) {
t[p].tag=-1;
t[p].tim=t[p].mx=t[p].sum=0;
if(l==r) return;
build(l,mid,ls);
build(mid+1,r,rs);
}
void modify(int l,int r,int v,int p) {
t[p].tim+=v;
t[p].mx+=a[r]*v;
t[p].sum+=(sum[r]-sum[l-1])*v;
}
inline void pushdown(int p,int l,int r) {
if(t[p].tag!=-1) {
t[ls].mx=t[rs].mx=t[p].tag;
t[ls].tag=t[rs].tag=t[p].tag;
t[ls].sum=1ll*t[p].tag*(mid-l+1);
t[rs].sum=1ll*t[p].tag*(r-mid);
t[ls].tim=t[rs].tim=0;
t[p].tag=-1;
}
if(t[p].tim) {
modify(l,mid,t[p].tim,ls);modify(mid+1,r,t[p].tim,rs);
t[p].tim=0;
}
}
inline int find(int l,int r,int u,int p) {
if(l==r) return l;
pushdown(p,l,r);
if(u<=t[ls].mx) return find(l,mid,u,ls);
else return find(mid+1,r,u,rs);
}
inline int query(int l,int r,int L,int R,int p) {
if(L<=l&&r<=R) return t[p].sum;
pushdown(p,l,r);
int ans=0;
if(L<=mid) ans+=query(l,mid,L,R,ls);
if(R>mid) ans+=query(mid+1,r,L,R,rs);
return ans;
}
inline void cover(int l,int r,int L,int R,int x,int p) {
if(L<=l&&r<=R) {
t[p].tag=x;t[p].mx=x;t[p].sum=(r-l+1)*x;t[p].tim=0;
return;
}
pushdown(p,l,r);
if(L<=mid) cover(l,mid,L,R,x,ls);
if(R>mid) cover(mid+1,r,L,R,x,rs);
t[p].sum=t[ls].sum+t[rs].sum;
t[p].mx=t[rs].mx;
}
signed main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=read();m=read();
for(int i=1;i<=n;i++)
a[i]=read();
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+a[i];
build(1,n,1);
int last=0;
while(m--) {
int d=read(),b=read();
modify(1,n,d-last,1);//修改时间
last=d;
int pos=find(1,n,b,1);
if(t[1].mx<b) {
puts("0");continue;
}
int ans=query(1,n,pos,n,1);
printf("%lld\n",ans-(b*(n-pos+1)));
cover(1,n,pos,n,b,1);
}
return 0;
}