1、考虑分块用vector存每个块的元素,这样使得快为可变大小同时如果块过大就重构这个块
cdq(树状数组维护的三维偏序问题):
void cdq(int l,int r){
if(l==r) return ;
int mid=(l+r)>>1;
cdq(l,mid);cdq(mid+1,r);
int i=l,j=mid+1,p=l;
while (i<=mid || j<=r){ //所有值都加入树状数组,后面再删掉
if (j>r || (i<=mid && q2[i].y<=q2[j].y))add(q2[i].z,q2[i].cnt),t[p++]=q2[i++];
//这里即便j已经超过r了,左边的y没加完,接着加完,只是不计算(j>r)
else q2[j].sum+=summ(q2[j].z),t[p++]=q2[j++];
}
for (i=l; i<=mid; ++i) add(q2[i].z,-q2[i].cnt);
for(int i=l;i<=r;i++)q2[i]=t[i];
}
整体二分:
思路:把所有值记录下标id,按结构体排序,然后二分答案,把所有比答案小的值的下标放入树状数组中,这里有个小技巧。
那就是因为每次区间都是往左走,然后一点点往右边走。那么就只有从大区间到左边小区间的时候,需要这个双指针往左边移动,把加过大于此时mid的值再退回来,那么对于每次查询summ(q[i].r)-summ(q[i].l-1)就是这个下标区间在树状数组中的个数,也就是值<=mid的个数,如果个数大于等于k,说明结果值必然小于mid,否则大于mid,此时判断丢到两边再进行查找。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=1e5+7;
const int INF=1e9+7;
struct ttt{
int id,key;
};
struct tt1{
int l,r,k,id;
};
int res[maxn],NN;
ttt q1[maxn];
tt1 q2[maxn];
int lowbits(int x){
return x&(-x);
}
int c1[maxn];//注意这里空间可能要开大一些
int summ(int x){
int sum1=0;
while(x>0){
sum1+=c1[x];
x-=lowbits(x);
}
return sum1;
}
int add(int x,int y){
while(x<=NN){
c1[x]+=y;
x+=lowbits(x);
}
}
int n,m,tot;
tt1 L[maxn],R[maxn];
void solve(int l,int r,int ql,int qr){
if(ql>qr||l>r)return ;
if(l==r){
for(int i=ql;i<=qr;i++){
res[q2[i].id]=l;
}return ;
}
int mid=(l+r)>>1;
int tot1,tot2;tot1=tot2=0;
while(tot<n&&q1[tot+1].key<=mid)tot++,add(q1[tot].id,1);
//因为有序,把所有结果满足的值放入树状数组中
while(tot>0&&q1[tot].key>mid)add(q1[tot].id,-1),tot--;
int sum1;
for(int i=ql;i<=qr;i++){
sum1=summ(q2[i].r)-summ(q2[i].l-1);
if(sum1>=q2[i].k)L[++tot1]=q2[i]; //个数太多值应该在左边
else R[++tot2]=q2[i];
}
for(int i=1,j=ql;i<=tot1;i++,j++)q2[j]=L[i];
for(int i=1,j=ql+tot1;i<=tot2;i++,j++)q2[j]=R[i];
solve(l,mid,ql,ql+tot1-1);
solve(mid+1,r,ql+tot1,qr);
}
int cmp1(ttt x,ttt y){
return x.key<y.key;
}
int main(){
int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,t5;
//freopen("in.txt","r",stdin);
scanf("%d %d",&n,&m);
NN=n;
int min1=2e9,max1=-2e9;
for(i=1;i<=n;i++){
q1[i].id=i;scanf("%d",&q1[i].key);
min1=min(min1,q1[i].key);max1=max(max1,q1[i].key);
}
sort(q1+1,q1+1+n,cmp1);
for(i=1;i<=m;i++){
scanf("%d %d %d",&q2[i].l,&q2[i].r,&q2[i].k);q2[i].id=i;
}
solve(min1,max1,1,m);
for(i=1;i<=m;i++)
printf("%d\n",res[i]);
return 0;
}
带修改的区间第k大(整体二分):
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=1e6+7;
const int INF=1e9+7;
struct ttt{
int id,key;
};
struct tt1{
int l,r,k,id,mark,ans; //mark表示其查询还是修改
};
int res[maxn],NN;
ttt q1[maxn];
tt1 q2[maxn];
int n,m,tot,id[maxn+maxn];
int lowbits(int x){
return x&(-x);
}
int c[2][maxn*3];//2个树状数组,注意这里空间可能要开大一些
int summ(int x,int num){
int sum1=0;
while(x>0){
sum1+=c[num][x];
x-=lowbits(x);
}
return sum1;
}
void add(int x,int y,int num){
while(x<=NN){
c[num][x]+=y;
x+=lowbits(x);
}
}
int L[maxn],R[maxn],effect[maxn];
void solve(int l,int r,int ql,int qr){
if(ql>qr||l>r)return ;
if(l==r){
for(int i=ql;i<=qr;i++){
q2[id[i]].ans=l;
}return ;
}
int mid=(l+r)>>1;
int tot1,tot2;tot1=tot2=0;
while(tot<n&&q1[tot+1].key<=mid)tot++,add(q1[tot].id,1,0);
//因为有序,把所有结果满足的值放入树状数组中
while(tot>0&&q1[tot].key>mid)add(q1[tot].id,-1,0),tot--;
int sum1;
for(int i=ql;i<=qr;i++){ //用id进行储存,那么每次查询与修改都是找的id对应的q2
if(q2[id[i]].mark==0){ //为0表示查询
sum1=summ(q2[id[i]].r,0)-summ(q2[id[i]].l-1,0)+effect[id[i]];
sum1+=summ(q2[id[i]].r,1)-summ(q2[id[i]].l-1,1);//还要考虑新修改的影响
if(sum1>=q2[id[i]].k){
L[++tot1]=id[i]; //个数太多值应该在左边
}
else R[++tot2]=id[i],effect[id[i]]+=summ(q2[id[i]].r,1)-summ(q2[id[i]].l-1,1);
}else{ //修改
if(q2[id[i]].r<=mid){
add(q2[id[i]].l,q2[id[i]].mark,1); //加与减一起操作了
L[++tot1]=id[i];
}else{
R[++tot2]=id[i];
}
}
}
for(int i=ql;i<=qr;i++)
if(q2[id[i]].mark!=0&&q2[id[i]].r<=mid)
add(q2[id[i]].l,-1*q2[id[i]].mark,1);
for(int i=1,j=ql;i<=tot1;i++,j++)id[j]=L[i];
for(int i=1,j=ql+tot1;i<=tot2;i++,j++)id[j]=R[i];
solve(l,mid,ql,ql+tot1-1);
solve(mid+1,r,ql+tot1,qr);
}
int cmp1(ttt x,ttt y){
return x.key<y.key;
}
char s1[10];
int B[maxn],cnt;
int main(){
int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,t5,T;
//freopen("in.txt","r",stdin);
//freopen("out1.txt","w",stdout);\
tot=cnt=0;memset(effect,0,sizeof(effect));
scanf("%d %d",&n,&m);
NN=n;
int min1=0,max1=1e9;
for(i=1;i<=n;i++){
q1[i].id=i;scanf("%d",&q1[i].key);B[i]=q1[i].key;
}
sort(q1+1,q1+1+n,cmp1);
for(i=1;i<=m;i++){
scanf("%s",&s1);q2[++cnt].id=i;
if(s1[0]=='Q'){
scanf("%d %d %d",&q2[cnt].l,&q2[cnt].r,&q2[cnt].k);q2[cnt].mark=0;
}else{
scanf("%d %d",&t1,&t2);//这里为把l的值改为r,1表示+
q2[cnt].mark=1;q2[cnt].l=t1;q2[cnt].r=t2;//值的改变相当于加上一个数再减去原来
cnt++;q2[cnt].mark=-1;q2[cnt].l=t1;q2[cnt].r=B[t1];
B[t1]=t2;
}
}
for(i=1;i<=cnt;i++)id[i]=i;
solve(min1,max1,1,cnt);
for(i=1;i<=cnt;i++)
if(q2[i].mark==0)
printf("%d\n",q2[i].ans);
return 0;
}
树的直径:有那么一个结论从任意一点开始跑,到最远的那个点u是直径之一,然后再u跑一次,最远那个点v与u的距离为直径、
pb_ds,可并堆的用法:
题意:
有n个猴子,每个猴子有攻击力,每次找出u与v所在团队的最大攻击力p和q进行打架,打架以后p和q的攻击力降为一半,然后这两个团队进行合并,开始每个猴子跟自己是一个团队的。
pb_ds的用法,对于这里的堆来说,删除就是删除堆顶,因为要维护的就是最值
#include<bits/stdc++.h>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;
using namespace __gnu_pbds;
const int maxn=1e5+7;
struct Set{
int pre[maxn];
void init(int n){
for(int i=1;i<=n;i++)
pre[i]=i;
}
int find1(int u){
int x,y;
x=u;
while(u!=pre[u]){
u=pre[u];
}
while(pre[x]!=u){
y=pre[x];
pre[x]=u;
x=y;
}return u;
}
void Union(int u,int v){
u=find1(u);v=find1(v);
if(u!=v){
pre[v]=u; //把v合并到u上面去
}
}
}s;
#define param int,less<int>,pairing_heap_tag //从大到小排序
//greater<int>表示使用的为从小到大排序,默认使用pairing_heap_tag(配对堆)
__gnu_pbds::priority_queue<param>G[maxn];
int main(){
//freopen("in.txt","r",stdin);
int i,j,k,f1,f2,f3,f4,f5,t1,t2,t3,t4,n,m,T;
int u,v;
while(scanf("%d",&n)==1){
s.init(n);
for(i=1;i<=n;i++){
G[i].clear();
scanf("%d",&t1);
G[i].push(t1);
}
scanf("%d",&m);
for(i=1;i<=m;i++){
scanf("%d %d",&u,&v);
f1=s.find1(u);f2=s.find1(v);
if(f1==f2){
printf("-1\n");continue;
}
t1=G[f1].top();t2=G[f2].top();
G[f1].pop();G[f2].pop(); //每次删除都是删除堆顶部
G[f1].push(t1/2);G[f2].push(t2/2);
G[f1].join(G[f2]); //插入一个堆会把另外一个堆给删除掉
cout <<G[f1].top() <<endl;
s.Union(f1,f2);
}
}
return 0;
}
树链剖分(剖点,区间和+区间最大值):
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+7;
int tot,head[maxn]; //链式前向星
int pos,n,son[maxn],deep[maxn],fa[maxn],num[maxn];
//分别表示点的重的儿子,深度,父亲,子树大小
int p[maxn],fp[maxn],tp[maxn],q1[maxn];
//p表示这个点的下标,fp表示这个下标对应的点,tp表示这个点的重链
struct Edge{
int u,v,c;
}edge[maxn];
struct E{
int next,to;
}e[maxn+maxn]; //链式前向星要开双倍
void add(int u,int v){
tot++;
e[tot].to=v;
e[tot].next=head[u];
head[u]=tot;
}
int Sum,Max;
void init(){
pos=0;
memset(son,-1,sizeof(son));
}
void dfs1(int u,int pre,int w){
deep[u]=w;fa[u]=pre;num[u]=1;int v;
for(int i=head[u];i!=-1;i=e[i].next){ //next表示下一个头,to表示指向的点
v=e[i].to;
if(v==pre)continue;
dfs1(v,u,w+1);
num[u]+=num[v];
if(son[u]==-1||num[v]>num[son[u]])
son[u]=v;
}
}
void dfs2(int u,int sd){//p表示u结点代表的下标,fp表示这个下标对应的点
p[u]=++pos;fp[pos]=u;tp[u]=sd;
if(son[u]==-1)return ;
dfs2(son[u],sd);
int v;
for(int i=head[u];i!=-1;i=e[i].next){
v=e[i].to;
if(v==son[u]||v==fa[u])continue;
dfs2(v,v);
}
}
struct ttt{
int l,r,sum,max1;
};
ttt T[maxn*4];
void push_up(int num){
T[num].sum=T[num+num].sum+T[num+num+1].sum;
T[num].max1=max(T[num+num].max1,T[num+num+1].max1);
}
int A[maxn];
void build(int l,int r,int num){
T[num].l=l;
T[num].r=r;
if(l==r){ //对于边权树剖,点0的值为0
T[num].sum=T[num].max1=A[fp[l]];
return ;
}
int mid=(l+r)/2;
build(l,mid,num+num);
build(mid+1,r,num+num+1);
push_up(num);
}
void update(int l,int r,int w,int pos,int num){
if(l==r&&l==pos){
T[num].sum=w;T[num].max1=w;
}else{
int mid=(l+r)/2;
if(pos<=mid)update(l,mid,w,pos,num+num);
else update(mid+1,r,w,pos,num+num+1);
push_up(num);
}
}
void query(int l,int r,int l1,int r1,int num){
if(l1<=l&&r1>=r){
Sum+=T[num].sum;Max=max(Max,T[num].max1);
}else{
int mid=(l+r)/2;
if(l1<=mid)query(l,mid,l1,r1,num+num);
if(r1>mid)query(mid+1,r,l1,r1,num+num+1);
}
}
int LCA(int u,int v){
int uu=tp[u],vv=tp[v];
int t1=u,t2=v;Sum=0;
Max=-1e9;
while(uu!=vv){
if(deep[vv]>deep[uu]){
swap(uu,vv);swap(u,v);
}
query(1,n,p[uu],p[u],1);
u=fa[uu];
uu=tp[u];
}
if(deep[u]>deep[v]){
swap(u,v);
}query(1,n,p[u],p[v],1);//这里减去了lca的值,因为往右边多走了一步
return Sum;
}
char s1[10];
int main(){
int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,m;
//freopen("in.txt","r",stdin); //链式前向星开始赋值为-1
//freopen("out1.txt","w",stdout);//
int left,right,mid,min1;
scanf("%d",&n);
tot=0;
memset(head,-1,sizeof(head));
for(i=1;i<n;i++){
scanf("%d %d",&t1,&t2);
add(t1,t2);add(t2,t1);
}
for(i=1;i<=n;i++)
scanf("%d",&A[i]);
init();
dfs1(1,1,1);
dfs2(1,1);
build(1,n,1);
scanf("%d",&m);
for(i=1;i<=m;i++){
scanf("%s",s1);
if(s1[1]=='M'){ //求区间最大值
scanf("%d %d",&t1,&t2);
LCA(t1,t2);
printf("%d\n",Max);
}else if(s1[1]=='S'){ //求区间和
scanf("%d %d",&t1,&t2);
LCA(t1,t2);
printf("%d\n",Sum);
}else{
scanf("%d %d",&t1,&t2);
update(1,n,t2,p[t1],1);
push_up(1);
}
}
return 0;
}
树链剖分(剖边):
每次把边的值给其子节点,那么结果就是点权的结果减去他们lca的值:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e5+7;
ll tot,head[maxn]; //链式前向星
ll pos,n,son[maxn],deep[maxn],fa[maxn],num[maxn];
//分别表示点的重的儿子,深度,父亲,子树大小
ll p[maxn],fp[maxn],tp[maxn],q1[maxn];
//p表示这个点的下标,fp表示这个下标对应的点,tp表示这个点的重链
struct Edge{
ll u,v,c;
}edge[maxn];
struct E{
ll next,to;
}e[maxn+maxn]; //链式前向星要开双倍
void add(ll u,ll v){
tot++;
e[tot].to=v;
e[tot].next=head[u];
head[u]=tot;
}
ll Sum;
void init(){
pos=0;
memset(son,-1,sizeof(son));
}
ll dfs1(ll u,ll pre,ll w){
deep[u]=w;fa[u]=pre;num[u]=1;
ll v;
for(ll i=head[u];i!=-1;i=e[i].next){ //next表示下一个头,to表示指向的点
v=e[i].to;
if(v==pre)continue;
dfs1(v,u,w+1);
num[u]+=num[v];
if(son[u]==-1||num[v]>num[son[u]])
son[u]=v;
}
}
void dfs2(ll u,ll sd){//p表示u结点代表的下标,fp表示这个下标对应的点
p[u]=++pos;fp[p[u]]=u;tp[u]=sd;
if(son[u]==-1)return ;
dfs2(son[u],sd);
ll v;
for(ll i=head[u];i!=-1;i=e[i].next){
v=e[i].to;
if(v==son[u]||v==fa[u])continue;
dfs2(v,v);
}
}
struct ttt{
ll l,r,sum;
};
ttt T[maxn*4];
void push_up(ll num){
T[num].sum=T[num+num].sum+T[num+num+1].sum;
}
void build(ll l,ll r,ll num){
T[num].l=l;
T[num].r=r;
if(l==r){ //对于边权树剖,点0的值为0
if(l!=1){
T[num].sum=q1[fp[l]];
}else{
T[num].sum=0;
}
return ;
}
ll mid=(l+r)/2;
build(l,mid,num+num);
build(mid+1,r,num+num+1);
push_up(num);
}
ll update(ll l,ll r,ll w,ll pos,ll num){
if(l==r&&l==pos){
T[num].sum=w;
}else{
ll mid=(l+r)/2;
if(pos<=mid)update(l,mid,w,pos,num+num);
else update(mid+1,r,w,pos,num+num+1);
push_up(num);
}
}
ll query(ll l,ll r,ll l1,ll r1,ll num){
if(l1<=l&&r1>=r){
Sum+=T[num].sum;
}else{
ll mid=(l+r)/2;
if(l1<=mid)query(l,mid,l1,r1,num+num);
if(r1>mid)query(mid+1,r,l1,r1,num+num+1);
}
}
ll LCA(ll u,ll v){
ll uu=tp[u],vv=tp[v];
ll t1=u,t2=v;Sum=0;
while(uu!=vv){
if(deep[vv]>deep[uu]){
swap(uu,vv);swap(u,v);
}
query(1,n,p[uu],p[u],1);
u=fa[uu];
uu=tp[u];
}
if(deep[u]>deep[v]){
swap(u,v);
}query(1,n,p[u]+1,p[v],1);//这里减去了lca的值,因为往右边多走了一步
return Sum;
}
int main(){
ll i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,m;
freopen("in.txt","r",stdin); //链式前向星开始赋值为-1
freopen("out1.txt","w",stdout);//
ll left,right,mid;
ll min1;
scanf("%lld %lld",&n,&m);
tot=0;
memset(head,-1,sizeof(head));
for(i=1;i<n;i++){
scanf("%lld %lld %lld",&t1,&t2,&t3);
edge[i].u=t1;edge[i].v=t2;edge[i].c=t3;
add(t1,t2);add(t2,t1);
}
init();
dfs1(1,1,1);
dfs2(1,1);
for(i=1;i<n;i++){ //对n-1条边考虑权值给谁
if(deep[edge[i].u]>deep[edge[i].v]){ //把边权给深度较深的点
q1[edge[i].u]=edge[i].c;
}else{
q1[edge[i].v]=edge[i].c;
}
}
build(1,n,1);
for(i=1;i<=m;i++){
scanf("%lld",&f1);
if(f1==1){
scanf("%lld %lld",&t1,&t2);
LCA(t1,t2);
printf("%lld\n",Sum);
}else{
scanf("%lld %lld",&t1,&t2);
if(deep[edge[t1].u]>deep[edge[t1].v]){ //每次更新一条边就相当于更新下面的那个结点的值
update(1,n,t2,p[edge[t1].u],1); //t2表示值
}else{
update(1,n,t2,p[edge[t1].v],1);
}
push_up(1);
}
}
return 0;
}
主席树(xdoj):
单点修改,求第k棵树的某个区间和
#include <iostream>
#include <stdio.h>
#include <vector>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn=1e6+7;
int cnt;
struct ttt{
int l,r,sum;
};
int root[maxn],X,C;
ttt T[maxn*5];
int update(int l,int r,int &x,int y,int pos){
T[++cnt]=T[y];
x=cnt;
int x1=cnt;
if(l==r){
T[x1].sum+=X;
C=T[x1].sum;
return 0;
}
int mid=(l+r)/2;
if(pos<=mid) {
update(l,mid,T[x].l,T[y].l,pos);
}else{
update(mid+1,r,T[x].r,T[y].r,pos);}
T[x1].sum=0;
if(T[x1].l)
T[x1].sum=T[T[x1].l].sum;
if(T[x1].r)
T[x1].sum+=T[T[x1].r].sum;
}
int Sum;
int query(int l,int r,int l1,int r1,int num){
if(l1<=l&&r1>=r){
Sum+=T[num].sum;return 0;
}else{
int mid=(l+r)/2;
if(l1>mid){
if(T[num].r!=0)query(mid+1,r,l1,r1,T[num].r);
}else if(r1<=mid){
if(T[num].l!=0)query(l,mid,l1,r1,T[num].l);
}else{
if(T[num].r!=0)query(mid+1,r,l1,r1,T[num].r);
if(T[num].l!=0)query(l,mid,l1,r1,T[num].l);
}
}
}
int main(){
int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,n,m;
//freopen("in.txt","r",stdin);
cin >> n>> m;
C=0;
int l1,r1;
int ro1=1;
for(i=1;i<=m;i++){
scanf("%d",&t1);
if(t1==1){
scanf("%d %d",&t2,&X);
C=t2^C;
update(1,n,root[ro1],root[ro1-1],C); //每次对C增加一个值X
ro1++;
}else{
scanf("%d %d %d",&t2,&t3,&t4);
l1=t2^C;r1=t3^C;
Sum=0; query(1,n,l1,r1,root[t4]); //查询第t4颗树的区间值[l1,r1]和
C=Sum;
printf("%d\n",Sum);
}
}
return 0;
}
主席树(区间第k大):
按照序列建树,然后然后一个区间的值就是2个主席树相减,判断左边的sum个数是否够k来判断走到哪边
#include <iostream>
#include <stdio.h>
#include <vector>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn=1e5+7;
int n,m,cnt;
struct ttt{
int l,r,sum; //sum表示这个区间里面数字的数量
};
int a[maxn];
int root[maxn];
ttt T[maxn*40];
vector<int>v;
int getid(int x){
return lower_bound(v.begin(),v.end(),x)-v.begin()+1; //错1次,返回下标需要-v.begin()+1
}
void update(int l,int r,int &x,int y,int pos){
T[++cnt]=T[y]; //把T[y]全部给T[cnt],也就是全部给x
T[cnt].sum++;x=cnt; //x表明的是下标
if(l==r)return 0;
int mid=(l+r)/2;
if(pos<=mid) update(l,mid,T[x].l,T[y].l,pos);
else update(mid+1,r,T[x].r,T[y].r,pos);
}
int query(int l,int r,int x,int y,int k){//query为查询,root查的是下标,那么输入就为下标
if(l==r)return l;
int mid=(l+r)/2;
int sum=T[T[y].l].sum-T[T[x].l].sum;//差值为在这个期间添加的结点
if(sum>=k)return query(l,mid,T[x].l,T[y].l,k);
else return query(mid+1,r,T[x].r,T[y].r,k-sum); //sum是指这个区间左边加了多少个值。
}
int main(){
int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4;
//freopen("in.txt","r",stdin);
cin >> n>> m;
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
v.push_back(a[i]);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(i=1;i<=n;i++){
update(1,n,root[i],root[i-1],getid(a[i])); //给线段树加入一个下标为a[i]的数
}
//v.erase(i),为删除下标是i的数字。
//v.erase(beg,end),删除从beg到end区间的数据。
for(i=1;i<=m;i++){
scanf("%d %d %d",&t1,&t2,&t3);
printf("%d\n",v[query(1,n,root[t1-1],root[t2],t3)-1]);
// 得到的下标是数字的下标,但是要在v中输出就必须-1
}
return 0;
}
主席树(区间修改+单点查询.建树HDU4866):
题意:给n条线段,每个线段距离x轴有一个距离k,有一个人会选定m个位置射击最近的k个线段,每个线段的价值为其到x轴的距离,问每次射击可以获得的价值。
思想:首先要找到距离x轴最近的k个线段,然后判断这k个线段距离x轴的距离分别是多少。
把线段按照x轴进行排序,然后二分第k个线段的位置,也就是主席树在点上有恰好k个线段的那颗主席树,然后主席树有一个下标存的是每次这个区间所加的值(距离),那么这个距离和最后就是获得的价值。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int MOD=1e9+7;
const int maxn=100003;
typedef long long ll;
int lll[64*maxn];
int rrr[64*maxn];
int num[64*maxn];
ll sum[64*maxn];
int root[maxn],n,m,cnt;
void update(int l1,int r1,int l,int r,int &x,int y,int w){
cnt++;
lll[cnt]=lll[y];rrr[cnt]=rrr[y];
num[cnt]=num[y];sum[cnt]=sum[y];
if(l>=l1&&r1>=r){
num[cnt]++; //表示这个区间的线段数量
sum[cnt]+=w;//这个区间的距离,也就是值
x=cnt;
return ;
}
x=cnt;
int mid=(l+r)/2;
if(l1<=mid)update(l1,r1,l,mid,lll[x],lll[y],w);
if(mid<r1)update(l1,r1,mid+1,r,rrr[x],rrr[y],w);
}
ll res1,res2;
int query(int l,int r,int x,int k){ //x这棵树的中找k
res1+=num[x];
res2+=sum[x];
if(l==r){
return 0;
}
int mid=(l+r)/2;
if(mid>=k)return query(l,mid,lll[x],k); //找这棵树的k这个点有几个
else return query(mid+1,r,rrr[x],k);
}
struct tt2{
int l,r,w;
};
tt2 qq[105000];
int cmp1(tt2 x,tt2 y){
return x.w<y.w;
}
int main(){
int i,j,k,f1,L,f3,f4,P,t1,t2,t3,t4;
ll f2;
// freopen("in.txt","r",stdin);
//freopen("out2.txt","w",stdout);
while(scanf("%d %d %d %d",&n,&m,&L,&P)==4){
//cin >> n >> m >> L >> P;
for(i=1;i<=n;i++){
scanf("%d %d %d",&qq[i].l,&qq[i].r,&qq[i].w);
}
memset(lll,0,sizeof(lll));
memset(rrr,0,sizeof(rrr));
memset(num,0,sizeof(num));
memset(root,0,sizeof(root));
memset(sum,0,sizeof(sum));
sort(qq+1,qq+1+n,cmp1);
cnt=0;
// root[0]=build(1,L);
for(i=1;i<=n;i++){
// cout <<"新的" << endl;
update(qq[i].l,qq[i].r,1,L,root[i],root[i-1],qq[i].w);
}
ll pre=1;
ll min1;
int left1,right1,mid1;
t2=0;
res1=0;
query(1,L,root[4],2);
for(i=1;i<=m;i++){
scanf("%d %d %d %d",&f1,&f2,&f3,&f4);
t1=((f2*pre)%f4+f3)%f4;
left1=0,right1=n;//有n棵树
min1=0;
while(right1>=left1){
mid1=(right1+left1)>>1;
res1=0;res2=0;
query(1,L,root[mid1],f1); //第mid1棵树,k这个结点的数量>=t1的最小值
if(res1<=t1){ //有可能目标的前面的个数并不满res1个,那么找右边的区间
left1=mid1+1;
min1=max(min1,res2);
}else{
right1=mid1-1;
}
}
if(t2==1){
pre=min1*2;
}else{
pre=min1;
}
if(pre>P){
t2=1;
}else{
t2=0;
}
printf("%lld\n",pre);
}
}
return 0;
}
带修改区间第k大(主席树):
#include <bits/stdc++.h>
const int MAXN=2e5+10;
using namespace std;
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;
}
typedef struct node{
int l,r,sum;
}node;
node d[MAXN*201];
int rt[MAXN];
int cnt;
int get_id(int x){return x&(-x);}
void update(int &x,int y,int l,int r,int t,int vul){
x=++cnt;d[x]=d[y];d[x].sum+=vul;
if(l==r)return ;
int mid=(l+r)>>1;
if(t<=mid)update(d[x].l,d[y].l,l,mid,t,vul);
else update(d[x].r,d[y].r,mid+1,r,t,vul);
}
int ans,sum;
vector<int>v1;
vector<int>v2;
void querty(int l,int r,int k){
if(l==r){ans=l;return ;}
sum=0;
int mid=(l+r)>>1;
for(int i=0;i<v1.size();i++)sum+=d[d[v1[i]].l].sum;
for(int i=0;i<v2.size();i++)sum-=d[d[v2[i]].l].sum;
if(k<=sum){
for(int i=0;i<v1.size();i++)v1[i]=d[v1[i]].l;
for(int i=0;i<v2.size();i++)v2[i]=d[v2[i]].l;
querty(l,mid,k);
}
else{
for(int i=0;i<v1.size();i++)v1[i]=d[v1[i]].r;
for(int i=0;i<v2.size();i++)v2[i]=d[v2[i]].r;
querty(mid+1,r,k-sum);
}
}
vector<int>vec;
int a[MAXN];
typedef struct Q{
int op,l,r,k;
}Q;
Q q[MAXN];
int main(){
int n,m;n=read();m=read();
for(int i=1;i<=n;i++)a[i]=read(),vec.push_back(a[i]);
char ch;
for(int i=1;i<=m;i++){
scanf(" %c",&ch);
if(ch=='Q'){q[i].op=1;q[i].l=read();q[i].r=read();q[i].k=read();}
else{q[i].op=2;q[i].l=read();q[i].r=read();vec.push_back(q[i].r);}
}
sort(vec.begin(),vec.end());
int sz=unique(vec.begin(),vec.end())-vec.begin();
for(int i=1;i<=n;i++){
a[i]=lower_bound(vec.begin(),vec.begin()+sz,a[i])-vec.begin()+1;
for(int j=i;j<=n;j+=get_id(j))update(rt[j],rt[j],1,sz,a[i],1);
}
for(int i=1;i<=m;i++){
if(q[i].op==2){
q[i].r=lower_bound(vec.begin(),vec.begin()+sz,q[i].r)-vec.begin()+1;
for(int j=q[i].l;j<=n;j+=get_id(j))update(rt[j],rt[j],1,sz,a[q[i].l],-1),update(rt[j],rt[j],1,sz,q[i].r,1);
a[q[i].l]=q[i].r;
}else{
v1.clear();v2.clear();
for(int j=q[i].r;j>0;j-=get_id(j))v1.push_back(rt[j]);
for(int j=q[i].l-1;j>0;j-=get_id(j))v2.push_back(rt[j]);
querty(1,sz,q[i].k);
printf("%d\n",vec[ans-1]);
}
}
return 0;
}
树剖+主席树,找u到v之间值在L,R的值总和:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
int tot,head[maxn]; //链式前向星
int pos,n,son[maxn],deep[maxn],fa[maxn],num[maxn];
//分别表示点的重的儿子,深度,父亲,子树大小
int p[maxn],fp[maxn],tp[maxn],q1[maxn];
//p表示这个点的下标,fp表示这个下标对应的点,tp表示这个点的重链
struct Edge{
int u,v,c;
}edge[maxn];
struct E{
int next,to;
}e[maxn+maxn]; //链式前向星要开双倍
void add(int u,int v){
tot++;
e[tot].to=v;
e[tot].next=head[u];
head[u]=tot;
}
char s1[5];
int B[maxn];
struct tt3{
int u,v,l,r;
};
tt3 qq1[maxn];
int cnt;
struct ttt{
int l,r,sum;
ll ans; //sum表示这个区间里面数字的数量
};
int root[maxn];
ttt T[maxn*40];
ll Sum;
void init(){
pos=0;
memset(son,-1,sizeof(son));
}
inline void dfs1(int u,int pre,int w){
deep[u]=w;fa[u]=pre;num[u]=1;int v;
for(int i=head[u];i!=-1;i=e[i].next){ //next表示下一个头,to表示指向的点
v=e[i].to;
if(v==pre)continue;
dfs1(v,u,w+1);
num[u]+=num[v];
if(son[u]==-1||num[v]>num[son[u]])
son[u]=v;
}
}
inline void dfs2(int u,int sd){//p表示u结点代表的下标,fp表示这个下标对应的点
p[u]=++pos;fp[pos]=u;tp[u]=sd;
if(son[u]==-1)return ;
dfs2(son[u],sd);
int v;
for(int i=head[u];i!=-1;i=e[i].next){
v=e[i].to;
if(v==son[u]||v==fa[u])continue;
dfs2(v,v);
}
}
int A[maxn];
inline void update(int l,int r,int &x,int y,int pos,int key){
T[++cnt]=T[y]; //把T[y]全部给T[cnt],也就是全部给x
T[cnt].ans+=key;
x=cnt; //x表明的是下标
if(l==r)return ;
int mid=(l+r)/2;
if(pos<=mid) update(l,mid,T[x].l,T[y].l,pos,key);
else update(mid+1,r,T[x].r,T[y].r,pos,key);
}
inline int query(int l,int r,int x,int y,int L,int R){//query为查询,root查的是下标,那么输入就为下标
if(L<=l&&R>=r){ //值在这个区间的这两个点间有多少个
Sum+=T[y].ans-T[x].ans;
}else{
int mid=(l+r)/2;
if(L<=mid) query(l,mid,T[x].l,T[y].l,L,R);
if(R>mid) query(mid+1,r,T[x].r,T[y].r,L,R); //sum是指这个区间左边加了多少个值。
}
}
int LEN;
vector<int>v;
inline int getid(int x){
return lower_bound(v.begin(),v.end(),x)-v.begin()+1; //错1次,返回下标需要-v.begin()+1
}
inline int LCA(int u,int u1,int L,int R){
int uu=tp[u],vv=tp[u1];int l,r;
int t1=u,t2=u1;Sum=0;
l=getid(L);
r=getid(R);
while(uu!=vv){
if(deep[vv]>deep[uu]){
swap(uu,vv);swap(u,u1);
}
query(1,LEN,root[p[uu]-1],root[p[u]],l,r);
u=fa[uu];
uu=tp[u];
}
if(deep[u]>deep[u1]){
swap(u,u1);
}
query(1,LEN,root[p[u]-1],root[p[u1]],l,r);//这里减去了lca的值,因为往右边多走了一步
return Sum;
}
inline int read(){
int f=1,x=0;char ch;
do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
return f*x;
}
int main(){
int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,m;
//freopen("in.txt","r",stdin); //链式前向星开始赋值为-1
//freopen("out1.txt","w",stdout);//
int left,right,mid,min1;
while(scanf("%d %d",&n,&m)==2){
v.clear();cnt=tot=0;
for(i=1;i<=n;i++){
B[i]=read();
v.push_back(B[i]);
}
memset(head,-1,sizeof(head));
for(i=1;i<n;i++){
t1=read();t2=read();
add(t1,t2);add(t2,t1);
}
for(i=1;i<=m;i++){
scanf("%d %d %d %d",&t1,&t2,&t3,&t4);
v.push_back(t3);v.push_back(t4);
qq1[i].u=t1;qq1[i].v=t2;qq1[i].l=t3;qq1[i].r=t4;
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end()); //这里记得需要排序才可以去重
LEN=v.size();
init();
dfs1(1,1,1);
dfs2(1,1);
for(i=1;i<=n;i++){
update(1,LEN,root[i],root[i-1],getid(B[fp[i]]),B[fp[i]]); //给线段树加入一个下标为a[i]的数
}
for(i=1;i<m;i++){
LCA(qq1[i].u,qq1[i].v,qq1[i].l,qq1[i].r);
printf("%lld ",Sum);
}
LCA(qq1[m].u,qq1[m].v,qq1[m].l,qq1[m].r);
printf("%lld\n",Sum);
}
return 0;
}
分块(区间加,单点查询):
#include<bits/stdc++.h>
using namespace std;
int l[5500],r[5500];
int belong[105000];
int q1[105000];
int n,block,num;
int mark[55005];
void build(){
double f1=n;
block=sqrt(f1*log(f1));
if(block==0)block=1; //属于第一块
num=n/block;if(n%block!=0)num++;
int i;
for(i=1;i<=num;i++){
l[i]=(i-1)*block+1;
r[i]=min(n,i*block); //每个block个边界,是包括的
}
for(i=1;i<=n;i++) //i属于哪个块
belong[i]=(i-1)/block+1;
}
int add(int x,int y,int t){ //对区间[x,y]操作均加t
int i;
if(belong[x]==belong[y]){ //在同一个块内
for(i=x;i<=y;i++)
q1[i]+=t;
}else{
for(i=x;i<=r[belong[x]];i++) //对块左边的单独处理
q1[i]+=t;
for(i=l[belong[y]];i<=y;i++) //对块右边的单独处理
q1[i]+=t;
int left1,right1,mid1;
for(i=belong[x]+1;i<belong[y];i++){ //对这些块打标机
mark[i]+=t;
}
}
}
int main(){
//freopen("in.txt","r",stdin);
int i,k,j,f1,f2,f3,f4,t1,t2,t3,t4,l2,m;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&q1[i]);
build();
for(i=1;i<=n;i++){
scanf("%d %d %d %d",&f1,&f2,&f3,&f4);
if(f1==0){ //区间[f2,f3]+f4
add(f2,f3,f4);
}else{ //查询f3的值
printf("%d\n",mark[belong[f3]]+q1[f3]);
}
}
return 0;
}
分块(区间加+找区间小于k的值有多少个):
注意排序的时候q1数组会更改顺序,不能单单q2改变的时候才去赋值,而是应该更改一个块的值。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
int l[5500],r[5500],belong[105000];
int q1[105000],q2[105000],q3[105000],mark[5005];
int n,block,num;
void build(){
double f1=n;
block=sqrt(f1);
if(block==0)block=1;
num=n/block;if(n%block!=0)num++;
int i;
for(i=1;i<=num;i++){
l[i]=(i-1)*block+1;
r[i]=min(n,i*block); //每个block个边界,是包括的
sort(q1+l[i],q1+r[i]+1); //注意这里的排序,后面是需要+1的,排序左闭右开
}//这里为第i块的左区间与右区间
for(i=1;i<=n;i++){//
belong[i]=(i-1)/block+1;
}
}
int add(int x,int y,int t){
int i,res=0,ans=0;
if(belong[x]==belong[y]){
for(i=x;i<=y;i++){
q2[i]+=t;
}
for(i=l[belong[x]];i<r[belong[x]]+1;i++)
q1[i]=q2[i];
sort(q1+l[belong[x]],q1+r[belong[x]]+1);
}else{
for(i=x;i<=r[belong[x]];i++){
q2[i]+=t;
}
for(i=l[belong[x]];i<r[belong[x]]+1;i++)
q1[i]=q2[i];
sort(q1+l[belong[x]],q1+r[belong[x]]+1); //q1为排序后的正确结果
for(i=l[belong[y]];i<=y;i++){
q2[i]+=t;
}
for(i=l[belong[y]];i<r[belong[y]]+1;i++)
q1[i]=q2[i];
sort(q1+l[belong[y]],q1+r[belong[y]]+1);
int left1,right1,mid1;
for(i=belong[x]+1;i<belong[y];i++){
mark[i]+=t;
}
}
}
int find1(int x,int y,int k){
int K=k;K*=K;int i;
int sum1=0;int ans;
if(belong[x]==belong[y]){
for(i=x;i<=y;i++)
if(q2[i]+mark[belong[i]]<K)sum1++;
}else{
for(i=x;i<=r[belong[x]];i++){
if(q2[i]+mark[belong[i]]<K)sum1++;
}
for(i=l[belong[y]];i<=y;i++){
if(q2[i]+mark[belong[i]]<K)sum1++;
}
int left1,right1,mid1;
for(i=belong[x]+1;i<belong[y];i++){
ans=0;
left1=l[i];right1=r[i];
while(right1>=left1){
mid1=(right1+left1)/2;
if(q1[mid1]+mark[i]<K){
left1=mid1+1;
ans=max(ans,mid1-l[i]+1);
}else{
right1=mid1-1;
}
}
sum1+=ans;
}
}
return sum1;
}
int main(){
//freopen("in.txt","r",stdin);
//freopen("out1.txt","w",stdout);
int i,k,j,f1,f2,f3,f4,t1,t2,t3,t4,l2,m;
long long c1,c2,c3,c4;
cin >> n;
for(i=1;i<=n;i++){
scanf("%d",&q1[i]);
q2[i]=q1[i];
}
build();
for(i=1;i<=n;i++){
scanf("%d %d %d %d",&t1,&t2,&t3,&t4);
if(t1==0){ //查询[t2,t3]全部+t4
add(t2,t3,t4);
}else{
printf("%d\n",find1(t2,t3,t4));
}
}
return 0;
}
线段树(区间加+区间乘+单点查值):
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
const int mod= 1e9+7;
struct tree{
long long val,add,mul;
}Tree[maxn<<2];
int N,M,P,A[maxn];
inline void Up(int num){ //每次Up为更新区间的值
Tree[num].val = (Tree[num<<1].val + Tree[num<<1|1].val)%P;
}
inline void build(int num,int l,int r){
Tree[num].mul = 1;
if(l==r){
Tree[num].val=A[l];Tree[num].add=0;
return;
}
int mid = (l+r)>>1;
build(num<<1,l,mid);
build(num<<1|1,mid+1,r);
Up(num);
}
inline void Down(int num,int lnum,int rnum,int L,int R){
Tree[L].val = ((Tree[num].mul*Tree[L].val) + (Tree[num].add*lnum))%P;
Tree[R].val = ((Tree[num].mul*Tree[R].val) + (Tree[num].add*rnum))%P;
Tree[L].mul*=Tree[num].mul;Tree[L].mul%=P;
Tree[R].mul*=Tree[num].mul;Tree[R].mul%=P;
Tree[L].add*=Tree[num].mul;Tree[R].add*=Tree[num].mul;
Tree[L].add+=Tree[num].add;Tree[R].add+=Tree[num].add;
Tree[L].add%=P;Tree[R].add%=P;
Tree[num].mul = 1;
Tree[num].add = 0;
}
inline void update_mul(int L,int R,long long x,int num,int l,int r){//乘x
if(L>r||R<l)return;
if(L<=l&&R>=r){
Tree[num].val = (Tree[num].val*x)%P;
Tree[num].mul *= x;Tree[num].mul%=P;
Tree[num].add *= x;Tree[num].add%=P;
return;
}
int mid = (l+r)>>1;
Down(num,mid-l+1,r-mid,num<<1,num<<1|1);
if(L<=mid)update_mul(L,R,x,num<<1,l,mid);
if(R>mid)update_mul(L,R,x,num<<1|1,mid+1,r);
Up(num);
}
inline void update_add(int L,int R,long long x,int num,int l,int r){
if(L>r||R<l)return;
if(L<=l&&R>=r){
Tree[num].val = (Tree[num].val+x*(r-l+1))%P;
Tree[num].add = (Tree[num].add+x)%P;
return;
}
int mid = (l+r)>>1;
Down(num,mid-l+1,r-mid,num<<1,num<<1|1);
if(L<=mid)update_add(L,R,x,num<<1,l,mid);
if(R>mid)update_add(L,R,x,num<<1|1,mid+1,r);
Up(num);
}
long long Val;
inline void query(int L,int R,int num,int l,int r){
if(L>r||R<l)return ;
if(L<=l&&R>=r){
Val=(Val+Tree[num].val)%P;
return ;
}
int mid = (l+r)>>1;
Down(num,mid-l+1,r-mid,num<<1,num<<1|1);
if(L<=mid)query(L,R,num<<1,l,mid);
if(R>mid)query(L,R,num<<1|1,mid+1,r);
}
int main(){
//freopen("in.txt","r",stdin);
scanf("%d%d%d",&N,&M,&P);
memset(Tree,0,sizeof(Tree));
for(int i=1;i<=N;i++)
scanf("%d",&A[i]);
build(1,1,N);
int opt,l,r;
long long x;
for(int i=1;i<=M;++i){
scanf("%d%d%d",&opt,&l,&r);
if(opt==1){
scanf("%lld",&x);
update_mul(l,r,x,1,1,N);
}else if(opt==2){ //区间+
scanf("%lld",&x);
update_add(l,r,x,1,1,N);
}else{
Val=0;
query(l,r,1,1,N);
printf("%lld\n",Val);
}
}
return 0;
}
字典树:
字典树:
#include <stdio.h>
#include <string.h>
#include<iostream>
#define MAX(a,b) ((a)>(b)?(a):(b))
#define NODE 3200010
#define N 300010
using namespace std;
int n;
int v[N];
int node;
int next[NODE][2];
int end[NODE];
int num[NODE][2];
bool vis[NODE];
void add(int cur,int k)
{
memset(next[node],0,sizeof(next[node]));
end[node]=0;
next[cur][k]=node++;
}
int cal(int x)
{
int i,k,cur=0,t1;
int res=0;
for(i=19;i>=0;i--)
{
k=((1<<i)&x)?1:0;
if(num[cur][k]>=1<<(i)){
res+=1<<i;
cur=next[cur][1-k];
}else{
cur=next[cur][k];
}
if(cur==0)break; //这里是为了当进入到一个个数为0的分支,可以直接break
}
//return (x^end[cur]); 如果是求最大值
return res;
}
int main()
{
int i,j,k,x,cur;
int ans,m;
//freopen("in.txt","r",stdin);
while(~scanf("%d %d",&n,&m))
{
node=1;
memset(next[0],0,sizeof(next[0]));
for(i=0;i<n;i++)
{
scanf("%d",&x);
if(vis[x])continue;
vis[x]=1;
v[i]=x;
cur=0;
for(j=19;j>=0;j--)
{
k=((1<<j)&x)?1:0;
if(next[cur][k]==0)add(cur,k);
num[cur][k]++;
cur=next[cur][k];
}
end[cur]=x;
}
int t1,t2;
t1=0;
for(ans=i=0;i<m;i++){ //求最大值是max(ans,cal(v[i]))
cin >> t2;
t1^=t2;
cout << cal(t1) << endl;
}
}
return 0;
}