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);
}