P3380 【模板】二逼平衡树(树套树)
题意
方法
线段树套平衡树
查询k的排名:把每个区间的排名加起来
查询排名为k:不能相加,只能二分了
修改:把包含这个数的区间对应的二叉树都修改了
查询前驱:将每个区间的前驱取max
查询后继:将每个区间的后继取min
时间复杂度
注意一下细节
心得
结构体方便
代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f==1?x:-x;
}
const int N=1e5+4,inf=2147483647;
int n,m,a[N];
namespace treap{
struct bala{
int w,siz,num,wei,ch[2];
}t[N*20];
int tot=0;
inline int newnode(int w){
t[++tot].w=w;
t[tot].wei=rand();
t[tot].num=t[tot].siz=1;
t[tot].ch[0]=t[tot].ch[1]=0;
return tot;
}
inline void pushup(int p){
t[p].siz=t[t[p].ch[1]].siz+t[t[p].ch[0]].siz+t[p].num;
}
inline void rotate(int &p,int d){
int y=t[p].ch[d];
t[p].ch[d]=t[y].ch[d^1];
t[y].ch[d^1]=p;
pushup(p);pushup(y);
p=y;
}
inline void insert(int &p,int w){
if(!p)p=newnode(w);
else if(t[p].w==w)++t[p].num;
else if(t[p].w>w){
insert(t[p].ch[0],w);
if(t[t[p].ch[0]].wei>t[p].wei)rotate(p,0);
}
else{
insert(t[p].ch[1],w);
if(t[t[p].ch[1]].wei>t[p].wei)rotate(p,1);
}
pushup(p);
}
inline void delet(int &p,int w){
if(t[p].w>w)delet(t[p].ch[0],w);
else if(t[p].w<w)delet(t[p].ch[1],w);
else{
if(t[p].num>1)t[p].num--;
else if(!t[p].ch[0]&&!t[p].ch[1])p=0;
else if(!t[p].ch[0]){
rotate(p,1);
delet(t[p].ch[0],w);
}
else if(!t[p].ch[1]){
rotate(p,0);
delet(t[p].ch[1],w);
}
else{
if(t[t[p].ch[0]].wei>t[t[p].ch[1]].wei){
rotate(p,0);
delet(t[p].ch[1],w);
}
else{
rotate(p,1);
delet(t[p].ch[0],w);
}
}
}
if(p)pushup(p);
}
inline int queryrank(int p,int k){//+1
if(!p)return 0;
if(t[p].w>k)return queryrank(t[p].ch[0],k);
if(t[p].w==k)return t[t[p].ch[0]].siz;
return t[t[p].ch[0]].siz+t[p].num+queryrank(t[p].ch[1],k);
}
inline int querypre(int p,int k){
if(!p)return -inf;
if(k<=t[p].w)return querypre(t[p].ch[0],k);
return max(t[p].w,querypre(t[p].ch[1],k));
}
inline int querysuf(int p,int k){
if(!p)return inf;
if(t[p].w<=k)return querysuf(t[p].ch[1],k);
return min(t[p].w,querysuf(t[p].ch[0],k));
}
}
namespace seg{
#define lc (p<<1)
#define rc (p<<1|1)
int rt[N<<2];
inline void build(int p,int l,int r){
for(int i=l;i<=r;i++)//!!
treap::insert(rt[p],a[i]);
if(l==r)return;
int mid=l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
}
inline void modify(int p,int l,int r,int x,int y){
treap::delet(rt[p],a[x]);
treap::insert(rt[p],y);
if(l==r)return;
int mid=l+r>>1;
if(x<=mid)modify(lc,l,mid,x,y);
else modify(rc,mid+1,r,x,y);
}
inline int queryrank(int p,int l,int r,int L,int R,int k){
if(L<=l&&r<=R)return treap::queryrank(rt[p],k);
int mid=l+r>>1,ret=0;
if(L<=mid)ret+=queryrank(lc,l,mid,L,R,k);
if(mid<R)ret+=queryrank(rc,mid+1,r,L,R,k);
return ret;
}
inline int querynum(int u,int v,int k){//不能像queryrank一样累加,故二分
int l=0,r=1e8,mid;
while(l<r){
mid=l+r+1>>1;
if(queryrank(1,1,n,u,v,mid)<k)l=mid;
else r=mid-1;
}
return r;
}
inline int querypre(int p,int l,int r,int L,int R,int k){
if(L<=l&&r<=R)return treap::querypre(rt[p],k);
int mid=l+r>>1,ret=-inf;
if(L<=mid)ret=max(ret,querypre(lc,l,mid,L,R,k));
if(mid<R)ret=max(ret,querypre(rc,mid+1,r,L,R,k));
return ret;
}
inline int querysuf(int p,int l,int r,int L,int R,int k){
if(L<=l&&r<=R)return treap::querysuf(rt[p],k);
int mid=l+r>>1,ret=inf;
if(L<=mid)ret=min(ret,querysuf(lc,l,mid,L,R,k));
if(mid<R)ret=min(ret,querysuf(rc,mid+1,r,L,R,k));
return ret;
}
}
int main(){
srand(time(0));
n=read();m=read();
for(int i=1;i<=n;i++)
a[i]=read();
seg::build(1,1,n);
for(int i=1,op,x,y,l,r,k;i<=m;i++){
op=read();
switch(op){
case 1:l=read();r=read();k=read();printf("%d\n",seg::queryrank(1,1,n,l,r,k)+1);break;
case 2:l=read();r=read();k=read();printf("%d\n",seg::querynum(l,r,k));break;
case 3:x=read();y=read();seg::modify(1,1,n,x,y);a[x]=y;break;
case 4:l=read();r=read();k=read();printf("%d\n",seg::querypre(1,1,n,l,r,k));break;
case 5:l=read();r=read();k=read();printf("%d\n",seg::querysuf(1,1,n,l,r,k));break;
}
}
return 0;
}
[CQOI2011]动态逆序对
题意
对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。
方法
线段树
我们只需知道删除元素的前面有几个比他大的,后面有几个比他小的,就可以算出减少的逆序对,可是序列要变啊
先用树状数组求出一开始的逆序对,算有几个数比自己大时,算的是区间,很容易想到可以用主席树,每次我们把删除的节点加入主席树,计算贡献时减去他们就好了
代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f==1?x:-x;
}
#define ll long long
const int N=1e5+4;
int n,m,a[N],pre[N],suf[N],pos[N];
ll ans=0;
namespace bit{
int t[N];
inline void add(int x,int v){
for(;x<=n;x+=x&-x)t[x]+=v;
}
inline int ask(int x){
int ret=0;
for(;x;x-=x&-x)ret+=t[x];
return ret;
}
inline void clear(){
memset(t,0,sizeof(t));
}
}
namespace segment{
struct tree{
ll sum;
int ls,rs;
}t[N*60];
int rt[N],tot=0;
inline void insert(int &p,int l,int r,int x){
if(!p)p=++tot;
++t[p].sum;
if(l==r)return;
int mid=l+r>>1;
if(x<=mid)insert(t[p].ls,l,mid,x);
else insert(t[p].rs,mid+1,r,x);
}
inline ll query(int p,int l,int r,int L,int R){
if(L<=l&&r<=R)return t[p].sum;
int mid=l+r>>1;
ll ret=0;
if(L<=mid)ret=query(t[p].ls,l,mid,L,R);
if(mid<R)ret+=query(t[p].rs,mid+1,r,L,R);
return ret;
}
inline ll find(int p1,int p2,int l,int r){
if(l>r)return 0;
ll ret=0;
for(;p2;p2-=p2&-p2)
ret+=query(rt[p2],1,n,l,r);
for(;p1;p1-=p1&-p1)
ret-=query(rt[p1],1,n,l,r);
return ret;
}
inline void modify(int p,int x){
for(;p<=n;p+=p&-p)insert(rt[p],1,n,x);
}
}
int main(){
n=read();m=read();
for(int i=1;i<=n;i++){
a[i]=read();
pos[a[i]]=i;
pre[i]=bit::ask(n-a[i]);
bit::add(n-a[i]+1,1);
}
bit::clear();
for(int i=n;i;i--){
suf[i]=bit::ask(a[i]-1);
ans+=suf[i];
bit::add(a[i],1);
}
for(int i=1,x,p;i<=m;i++){
x=read();p=pos[x];
printf("%lld\n",ans);
ans-=pre[p]+suf[p]-segment::find(0,p,x+1,n)-segment::find(p,n,1,x-1);
segment::modify(p,x);
//加入删除的元素,计算贡献时就减去这些已删除的数
}
return 0;
}
P3591 [POI2015]ODW
题意
给定一棵n个点的树,树上每条边的长度都为1,第i个点的权值为a[i]。Byteasar想要走遍这整棵树,他会按照某个1到n的全排列b走n-1次,第i次他会从b[i]点走到b[i+1]点,并且这一次的步伐大小为c[i]。对于一次行走,假设起点为x,终点为y,步伐为k,那么Byteasar会从x开始,每步往前走k步,如果最后不足k步就能到达y,那么他会一步走到y。请帮助Byteasar统计出每一次行走时经过的所有点的权值和。
方法
暴力数据结构——分块
如果步伐比分的块大小大,我们暴力
否则
记表示,从根节点到当前节点的路径上,经过该节点,每个节点经过一个节点的权值和,预处理之后我们就可以用分块做了
时间复杂度
心得
分块好难写啊,动不动就写错,代码能力还是太弱了
代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f==1?x:-x;
}
const int N=5e4+4,S=225;
struct edge{
int v,nxt;
}e[N<<1];
int first[N],cnt=0;
inline void add(int u,int v){
e[++cnt].v=v;e[cnt].nxt=first[u];first[u]=cnt;
}
int n,a[N],b[N],C[N],fa[N][20],dep[N],s[S+1][N];
inline void dfs1(int x){
for(int i=1;(1<<i)<=dep[x];i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=first[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v==fa[x][0])continue;
fa[v][0]=x;
dep[v]=dep[x]+1;
dfs1(v);
}
}
inline int LCA(int a,int b){
if(dep[a]<dep[b])swap(a,b);
int tmp=dep[a]-dep[b];
for(int i=0;i<20;i++)
if((1<<i)&tmp)a=fa[a][i];
if(a==b)return a;
for(int i=19;i>=0;i--)
if(fa[a][i]!=fa[b][i]){
a=fa[a][i];b=fa[b][i];
}
return fa[a][0];
}
inline int getfa(int a,int x){
for(int i=0;i<20;i++)
if((1<<i)&x)a=fa[a][i];
return a;
}
inline void dfs2(int x){
//s[i]表示根节点到i的路径上,每c个点取一个,且i恰好被取到(根不一定被取到)
int u=fa[x][0];
for(int i=1;i<=S;i++){
s[i][x]=s[i][u]+a[x];
u=fa[u][0];//
}
for(int i=first[x],v;i;i=e[i].nxt){
v=e[i].v;
if(v!=fa[x][0])dfs2(v);
}
}
int main(){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1,u,v;i<n;i++){
u=read();v=read();
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)b[i]=read();
for(int i=1;i<n;i++)C[i]=read();
dep[1]=1;dfs1(1);
dfs2(1);
for(int i=1,u,v,c,lca,ans,flca,fv,c1,c2;i<n;i++){
u=b[i];v=b[i+1];c=C[i];
lca=LCA(u,v);
if(c==1){
printf("%d\n",s[1][u]+s[1][v]-s[1][lca]-s[1][fa[lca][0]]);
continue;
}
// c1=(dep[u]-dep[lca])%c;
// c2=(dep[v]-dep[lca]+c1)%c;
// fv=getfa(lca,c2);
if(c<=S){
ans=s[c][u];
c1=(dep[u]-dep[lca])%c;
if(!c1)c1=c;//不含lca
for(int i=19;~i;i--)
if(dep[fa[u][i]]-dep[lca]>=c1)u=fa[u][i];
ans+=a[u]-s[c][u];
if(dep[u]+dep[v]-(dep[lca]<<1)>=c){//含lca
c2=c-dep[u]+dep[lca];
u=v;
for(int i=19;~i;i--)
if(dep[fa[u][i]]-dep[lca]>=c2)u=fa[u][i];//
c1=(dep[v]-dep[u])%c;
if(c1)ans+=a[v];
v=getfa(v,c1);
ans+=s[c][v]-s[c][u]+a[u];
}
else ans+=a[v];
printf("%d\n",ans);
continue;
// flca=getfa(lca,c-c1);
// ans=s[c][u]-s[c][flca];
// cout<<ans<<" ";
// flca=getfa(lca,c1);
// ans+=s[c][fv]-s[c][flca];
// printf("%d\n",ans);
// continue;
}
ans=0;
while(dep[u]-dep[lca]>c){
ans+=a[u];u=getfa(u,c);
}
ans+=a[u];
if(dep[u]+dep[v]-(dep[lca]<<1)>=c){
int c1=c-dep[u]+dep[lca];
u=v;
for(int i=16;~i;i--)
if(dep[fa[u][i]]-dep[lca]>=c1)u=fa[u][i];
c1=(dep[v]-dep[u])%c;
if(c1)ans+=a[v];
v=getfa(v,c1);
while(dep[v]-dep[u]>=c){
ans+=a[v];v=getfa(v,c);
}
ans+=a[v];
}
else ans+=a[v];
printf("%d\n",ans);
// ans=0;
// while(dep[u]>=dep[lca]){
// ans+=a[u];
// u=getfa(u,c);
// }
// while(dep[fv]>dep[lca]){
// ans+=a[fv];
// fv=getfa(fv,c);
// }
}
return (0-0);
}
P5357 【模板】AC自动机(二次加强版)
题意
方法
建AC自动机
有个优化,每次匹配到时,并不用跳指针,只用在该节点上记值,最后我们用建树,一遍,求出答案
心得
玄学优化
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+4,M=2e6+4;
int n,a[N][27],fail[N],siz[N],cnt=1,ed[N];
char t[N],s[M];
inline void getfail(){
fail[1]=0;
queue<int>q;
for(int i=0;i<26;i++)a[0][i]=1;
q.push(1);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0,u;i<26;i++){
u=a[x][i];
if(!u){
a[x][i]=a[fail[x]][i];
continue;
}
fail[u]=a[fail[x]][i];
q.push(u);
}
}
}
struct edge{
int v,nxt;
}e[N];
int first[N],tot=0;
inline void add(int u,int v){
e[++tot].v=v;e[tot].nxt=first[u];first[u]=tot;
}
inline void dfs(int x){
for(int i=first[x],v;i;i=e[i].nxt){
v=e[i].v;
dfs(v);
siz[x]+=siz[v];//统计反了
}
}
int main(){
scanf("%d",&n);
for(int i=1,len,p,c;i<=n;i++){
scanf("%s",t+1);
len=strlen(t+1);
p=1;
for(int j=1;j<=len;j++){
c=t[j]-'a';
if(!a[p][c])a[p][c]=++cnt;
p=a[p][c];
}
ed[i]=p;//结尾节点
}
getfail();
scanf("%s",s+1);
for(int i=1,p=1,len=strlen(s+1);i<=len;i++){
p=a[p][s[i]-'a'];
siz[p]++;//每个子串遍历次数
}
for(int i=1;i<=cnt;i++)
add(fail[i],i);
dfs(1);//累计次数
for(int i=1;i<=n;i++)
printf("%d\n",siz[ed[i]]);
return (0-0);
}
P3812 【模板】线性基
题意
给定n个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大。
方法
线性基是一个数的集合,并且每个序列都拥有至少一个线性基
三(四)大性质:
- 原序列里面的任意一个数都可以由线性基里面的一些数异或得到
- 线性基里面的任意一些数异或起来都不能得到0
- 线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的
推荐学习
可以用线性基解决,贪心选最大值
心得
真神奇
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f==1?x:-x;
}
int n,ans=0,a[51],p[101];
inline void solve(int x){
for(int i=50;i>=0;i--){
if(!(x>>i))continue;
if(!p[i]){p[i]=x;break;}
x^=p[i];
}
}
signed main(){
n=read();
for(int i=1;i<=n;i++)
solve(a[i]=read());
for(int i=50;i>=0;i--)
if((ans^p[i])>ans)ans^=p[i];
cout<<ans;
return 0;
}
CF126B Password
题意
你需要找到既是S的前缀又是S的后缀同时又在S中间出现过的最长子串
现在给你字符串S,你需要找到满足上述要求的子串T
方法
扩展KMP,不过实际好像叫
思想和差不多
用法大概就是字符串匹配吧。模式串+’$’+文本串是一个很好的选择。
求出数组后,如何保证前缀也是后缀呢?z[i]==n-i.直观理解上就是以这一位为开始的串有n-i位与前缀相同。显而易见这说的就是后缀和前缀相等。那如何保证这一个串在中间也出现过呢?遍历的过程中记录一个z[i]的最大值maxx,若这个maxx>=n-i,则说明前面至少出现过不短于他的一个和前缀相同的串
心得
最后答案的求得还不是很能求出,好要加油!!!!!!!!!!!!!
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+4;
int n,fail[N],z[N];
char s[N];
inline void Z_algorithm(){
for(int i=2,l=1,r=1,k;i<=n;i++){
if(i>r){
l=r=i;
while(r<=n&&s[r-l+1]==s[r])r++;
z[i]=r-l;r--;
}
else{
k=i-l+1;
if(z[k]<r-i+1)z[i]=z[k];
else{
l=i;
while(r<=n&&s[r-l+1]==s[r])r++;
z[i]=r-l;r--;
}
}
}
}
int main(){
scanf("%s",s+1);
n=strlen(s+1);
Z_algorithm();
int mx=0,pos=0;
for(int i=2;i<=n;i++){
if(z[i]==n-i+1&&mx>=n-i+1){pos=i;break;}//妙啊!!
mx=max(mx,z[i]);
}
if(!pos)printf("Just a legend");
else for(int i=1;i<=n-pos+1;i++)putchar(s[i]);
return (0-0);
}