B.xay loves monotonicity
題目鏈接
簡要題解
不難看出這是一道維護序列信息的數據結構題。
我們需要支持單點修改\(A\)序列,區間修改\(B\)序列,以及區間查詢一個特殊的值。
具體地說,對於一個詢問,我們要取出序列\(A\)在這個區間內,包含左端點的最長不降子序列,並把子序列的位置對應到序列\(B\)中得到一個\(0/1\)串。
我們需要求這個\(0/1\)串中有多少個值不同的相鄰對。
這可以說是一個經典題,應該也可以說是一個套路題。
如果我們要在一個區間內求一個最長不降子序列,那么這個區間內的最大值一定在子序列中,並且這個最大值一定在序列末尾。
基於這個性質,兩個區間的信息就可以合並,而線段樹很適合維護多個區間合並的信息。
我們在線段樹上維護區間內\(A\)序列的最大值,以及這個最大值所在位置對應的\(B\),若有多個最大值則維護最右邊的那一個。
我們需要解決的問題可以用一個\(Calc(l,r,a,b)\)來描述:如果當前序列末尾的數為\(a\),其對應的\(B\)值為\(b\),那么\([l,r]\)這段區間可以提供多少貢獻。
很明顯,加上這段區間后,序列的末尾,要么是這段區間的最大值,要么是原來的\(a\),於是我們可以接着往后面合並區間。
線段樹可以將一個詢問區間分成\(logn\)個線段樹節點維護的區間,我們已經知道如何合並區間,現在只需要對每個區間快速計算\(Calc(l,r,a,b)\)。
對於線段樹節點維護的一個區間來說,如果左邊\(A\)序列的最大值小於\(a\),那么左邊就沒有貢獻,直接遞歸右邊。
否則,合並左半邊區間后,不降子序列的末尾就是左半邊序列的最大值,設為\(a_l\)。
而我們已經知道\(a_l\)以及對應的\(b_l\),只要提前算出右半邊區間的\(Calc(l,r,a_l,b_l)\),記為\(C\),就可以直接加上貢獻,只遞歸左邊。
我們從下往上維護每個區間的\(C\),每次維護需要\(logn\)的遞歸計算,因此總復雜度是\(O(nlog^2n)\)的。
因此,我們需要在線段樹上維護三個值:\(A\)序列的區間最大值\(a\),\(a\)在\(B\)序列對應位置的值\(b\),當左區間最大值作為子序列末尾時右區間的\(C\)值。
對於\(A\)序列和\(B\)序列的修改,就是單點修改與區間修改,修改完及時\(Push\_up\)維護\(C\)值即可。
對於一個區間的詢問,在線段樹上可以拆成\(logn\)個區間,依次對每個區間調用\(Calc\)函數,再合並即可。
代碼如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
int n,Qs,A[MAXN],B[MAXN];
int Read()
{ int a=0,c=1; char b=getchar();
while(b!='-'&&(b<'0'||b>'9')) b=getchar();
if(b=='-') c=-1,b=getchar();
while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
return a*c;
}
int Max(int A,int B){ return A>B?A:B; }
namespace TREE
{ struct ELE{ int Mv,Ma,Mb; };
int Maxa[MAXN*4],Maxl[MAXN*4];
bool Maxb[MAXN*4],Lan[MAXN*4];
ELE operator + (ELE A,ELE B)
{ if(B.Ma>=A.Ma) return (ELE){A.Mv+B.Mv,B.Ma,B.Mb};
return (ELE){A.Mv+B.Mv,A.Ma,A.Mb};
}
void Push_down(int S)
{ if(!Lan[S]) return;
Maxb[S<<1]^=1,Lan[S<<1]^=1,Maxb[S<<1|1]^=1,Lan[S<<1|1]^=1,Lan[S]=0;
}
int Calc(int S,int Le,int Ri,int Num,int Nb,int Mid=0)
{ if(Le==Ri) return Maxa[S]>=Num?Maxb[S]!=Nb:0;
Mid=(Le+Ri)>>1,Push_down(S);
return Maxa[S<<1]>=Num?Calc(S<<1,Le,Mid,Num,Nb)+Maxl[S]:Calc(S<<1|1,Mid+1,Ri,Num,Nb);
}
void Push_up(int S,int Le,int Ri,int Mid=0)
{ if(Maxa[S<<1|1]<Maxa[S<<1]) Maxa[S]=Maxa[S<<1],Maxb[S]=Maxb[S<<1];
else Maxa[S]=Maxa[S<<1|1],Maxb[S]=Maxb[S<<1|1];
Mid=(Le+Ri)/2,Maxl[S]=Calc(S<<1|1,Mid+1,Ri,Maxa[S<<1],Maxb[S<<1]);
}
void Build(int S,int Le,int Ri,int Mid=0)
{ if(Le==Ri) return Maxa[S]=A[Le],Maxb[S]=B[Le],(void)0;
Mid=(Le+Ri)>>1,Build(S<<1,Le,Mid),Build(S<<1|1,Mid+1,Ri),Push_up(S,Le,Ri);
}
void Modify1(int S,int Le,int Ri,int Aim,int Num,int Mid=0)
{ if(Le==Ri) return Maxa[S]=Num,(void)0;
Mid=(Le+Ri)>>1,Push_down(S);
Aim<=Mid?Modify1(S<<1,Le,Mid,Aim,Num):Modify1(S<<1|1,Mid+1,Ri,Aim,Num),Push_up(S,Le,Ri);
}
void Modify2(int S,int Le,int Ri,int Al,int Ar,int Mid=0)
{ if(Al<=Le&&Ri<=Ar) return Lan[S]^=1,Maxb[S]^=1,(void)0;
Mid=(Le+Ri)/2,Push_down(S);
if(Al<=Mid) Modify2(S<<1,Le,Mid,Al,Ar),Push_up(S,Le,Ri);
if(Mid<Ar) Modify2(S<<1|1,Mid+1,Ri,Al,Ar),Push_up(S,Le,Ri);
Push_up(S,Le,Ri);
}
ELE Query(int S,int Le,int Ri,int Al,int Ar,int Num,int Nb,int Mid=0)
{ if(Al<=Le&&Ri<=Ar)
{ if(Maxa[S]<Num) return (ELE){0,Num,Nb};
return (ELE){Calc(S,Le,Ri,Num,Nb),Maxa[S],Maxb[S]};
}
Mid=(Le+Ri)>>1,Push_down(S);
ELE Ra=(ELE){0,Num,Nb},Rb=(ELE){0,Num,Nb};
if(Al<=Mid) Ra=Query(S<<1,Le,Mid,Al,Ar,Num,Nb),Rb=Ra,Rb.Mv=0;
if(Mid<Ar) Rb=Query(S<<1|1,Mid+1,Ri,Al,Ar,Ra.Ma,Ra.Mb);
return Ra+Rb;
}
int Query2(int S,int Le,int Ri,int Aim,int Mid=0)
{ if(Le==Ri) return Maxb[S];
return Push_down(S),Mid=(Le+Ri)/2,Aim<=Mid?Query2(S<<1,Le,Mid,Aim):Query2(S<<1|1,Mid+1,Ri,Aim);
}
}using namespace TREE;
int main()
{ n=Read();
for(int i=1;i<=n;i++) A[i]=Read();
for(int i=1;i<=n;i++) B[i]=Read();
Build(1,1,n),Qs=Read();
for(int i=1,K,T1,T2;i<=Qs;i++)
{ K=Read(),T1=Read(),T2=Read();
if(K==1) Modify1(1,1,n,T1,T2),A[T1]=T2;
if(K==2) Modify2(1,1,n,T1,T2);
if(K==3) printf("%d\n",Query(1,1,n,T1,T2,A[T1],Query2(1,1,n,T1)).Mv);
}
}
J.xay loves Floyd
題目鏈接
簡要題解
對於正確的\(dis\)數組,我們可以直接跑\(n\)次\(Dijkstra\),在大概\(O(n*mlogn)\)的時間內求出來。
對於不正確的\(dis\)數組,假設我們現在轉移\(dis[i][j]\),要判斷\(dis[i][j]\)是否正確。
什么時候\(dis[i][j]\)是對的呢?存在某個點\(k\),使得\(dis[i][k]\)和\(dis[k][j]\)是對的,且\(k\)在\(x\)到\(y\)的某一條最短路上。
由於我們只需要知道是否正確,而不需要知道具體的值,那么為了加速轉移,可以用\(bitset\)來維護。
具體地說,我們設三個\(bitset\)數組,\(F[i][j]\)表示\(dis[i][j]\)是否正確,\(G[i][j]\)表示\(dis[j][i]\)是否正確。
為了判斷\(k\)是否在\(i\)到\(j\)的最短路上,我們每次枚舉到一個\(i\),就處理一個\(Pot[j][k]\),表示\(k\)是否在\(i\)到\(j\)的最短路上。
如果我們有了這三個數組,對於枚舉到的\(i\)和\(j\)來說,只要\(F[i]\)、\(G[j]\)、\(Pot[j]\)三個集合有交,那么\(dis[i][j]\)就是正確的。
\(F[i][j]\)和\(G[i][j]\)很好維護,設置好初始狀態,之后在轉移時跟着更新即可。
初始狀態是什么?我們之后的轉移都需要依賴中間點\(k\),因此初始狀態要加入沒有中間點的情況,也就是兩點直接連邊為最短路的情況。
現在考慮如何求\(Pot[j][k]\)。
我們枚舉到一個\(i\)之后,就把所有點按照到\(i\)的最短路從小到大排序,設這個序列為\(s\),那么若\(k<j\),則\(dis[i][s_k]<dis[i][s_j]\)。
由於邊權為正,\(Pot[s_k][s_j]\)就一定是\(0\),換句話說,在序列\(s\)中,只有前面的點才可能成為后面的點到\(i\)的最短路中間點。
那么我們從前往后處理\(Pot[s_j]\),枚舉連接\(s_j\)的每一條邊,設邊的另一端點為\(s_k\),若\(dis[i][s_j]=dis[i][s_k]+w\),則將\(Pot[j]\)或上\(Pot[k]\)。
因為\((s_k,s_j)\)這條邊在最短路上,那么所有在\(s_k\)最短路上的點,都會在\(s_j\)的最短路上。
我們實際上只是用\(bitset\)加速了錯誤做法的過程,本質上還是在模擬。
注意\(Pot[i][j]\)是正確的,也就是上帝視角,\(F[i][j]\)和\(G[i][j]\)是我們在模擬錯誤做法時維護的。
時間復雜度\(O(n*mlogn+\frac{n^2m}{\omega})\)
代碼如下:
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int MAXN=2010;
const int Inf=1e9;
struct EDGE{ int u,v,w,Next; }Edge[MAXN*4];
int n,m,Es,Ns,First[MAXN],Dis[MAXN][MAXN];
int Ans,Ls,Seq[MAXN],Vis[MAXN];
bitset<MAXN>F[MAXN],G[MAXN],Pot[MAXN];
void Link(int u,int v,int w){ Edge[++Es]=(EDGE){u,v,w,First[u]},First[u]=Es; }
namespace DIJ
{ struct ELE{ int Np,Nd; };
bool operator < (ELE A,ELE B){ return A.Nd==B.Nd?A.Np<B.Np:A.Nd>B.Nd; }
priority_queue<ELE>Heap;
void Dijkstra(int St,int *Dis)
{ for(int i=1;i<=n;i++) Dis[i]=Inf;
Dis[St]=0,Heap.push((ELE){St,0});
for(int Now;!Heap.empty();)
{ Now=Heap.top().Np,Heap.pop(),Vis[Now]=0;
for(int i=First[Now],v,w;i!=-1;i=Edge[i].Next)
{ v=Edge[i].v,w=Edge[i].w;
if(Dis[v]<=Dis[Now]+w) continue ;
Dis[v]=Dis[Now]+w;
if(!Vis[v]) Vis[v]=1,Heap.push((ELE){v,Dis[v]});
}
}
}
}using namespace DIJ;
bool cmp(int A,int B){ return Dis[Ns][A]<Dis[Ns][B]; }
int main()
{ scanf("%d%d",&n,&m);
memset(First,-1,sizeof(First));
for(int i=1,u,v,w;i<=m;i++) scanf("%d%d%d",&u,&v,&w),Link(u,v,w);
for(int i=1;i<=n;i++) Dijkstra(i,Dis[i]),F[i].set(i),G[i].set(i);
for(int i=1,u,v,w;i<=Es;i++)
{ u=Edge[i].u,v=Edge[i].v,w=Edge[i].w;
if(Dis[u][v]==w) F[u].set(v),G[v].set(u);
}
for(int i=1;i<=n;i++)
{ for(int j=1;j<=n;j++) Seq[j]=j,Pot[j].reset(),Pot[j].set(j);
Ns=i,sort(Seq+1,Seq+n+1,cmp);
for(int j=1;j<=n;j++)
for(int k=First[Seq[j]],v,w;k!=-1;k=Edge[k].Next)
{ v=Edge[k].v,w=Edge[k].w;
if(Dis[i][v]==Dis[i][Seq[j]]+w) Pot[v]|=Pot[Seq[j]];
}
for(int j=1;j<=n;j++)
if((F[i]&G[j]&Pot[j]).any()) F[i].set(j),G[j].set(i);
}
for(int i=1;i<=n;i++) Ans+=F[i].count();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) Ans+=(Dis[i][j]==Inf);
printf("%d\n",Ans);
}
K.xay loves sequence
題目鏈接
簡要題解
我們每次的操作都是給一個區間加一或減一,那么很重要的一個轉化就是差分。
我們取出詢問的區間,重新從\(1\)編號,構造一個長為\(n+1\)的序列{\(b_i\)},滿足\(b_i=a_i-a_{i-1}\),默認\(a_0=a_{n+1}=0\)
如果不模\(k\),那么我們的任務變成了:每次操作可以在{\(b_i\)}中選一個位置加一,再選一個位置減一,求將{\(b_i\)}全消成\(0\)的最小操作次數。
這個最小操作次數很好算,就是\(\frac{1}{2}\sum_{i=1}^{n+1}|b_i|\),因為是差分序列,\(\sum_{i=1}^{n+1}b_i=0\),所以一定可以構造出最優方案,每次使\(\sum_{i=1}^{n+1}|b_i|\)減二。
現在考慮模\(k\),那么在差分數組上體現為一個新操作:在{\(b_i\)}上選一個位置加\(k\),再選一個位置減\(k\)。並且這個操作不計入答案。
如果我們要對某個\(b_i\)進行新操作,那么當\(b_i>0\)時,我們一定減\(k\),否則不優。同理,當\(b_i<0\)時,我們一定加\(k\)。
對於\(b_i=0\)的地方我們不需要動,因為動了肯定不會使答案更優。
當\(b_i>0\)時,修改后,貢獻由\(|b_i|\)變成了\(|b_i-k|\),即\(\Delta=k-2*b_i\),我們設\(c_i=k-2*b_i\)。
當\(b_i<0\)時,修改后,貢獻由\(|b_i|\)變成了\(|b_i+k|\),即\(\Delta=k+2*b_i\),我們設\(d_i=k+2*b_i\)。
為了使答案最優,新操作轉化成了:在\(c_i\)和\(d_i\)中各選\(m\)個位置,使得選出來的元素之和最小,其中\(m\)是任意非負整數。
對\(c_i\)和\(d_i\)排序之后,我們設\(e_i=c_i+d_i\),那么我們只需要取出所有\(e_i<0\)的元素即可,並且這些\(e_i\)一定在前\(m\)個位置,可以通過二分找到\(m\)。
現在考慮多個詢問,並且詢問的是一個區間,該如何處理。
排序的過程我們可以直接用桶實現,區間的處理也可以用主席樹來維護。
具體地說,我們建一棵值域主席樹,分別維護\(c_i\)和\(d_i\),那么對於一個詢問來說,我們主席樹作差可以取出區間內的\(c_i\)和\(d_i\)。
注意我們取出區間的\(c_i\)和\(d_i\)時,第一個數和最后一個數的值會有變化,我們可以先進行單點修改,求出答案之后復原即可。
然后我們二分\(m\),再在主席樹上進行類似求區間第\(K\)大的操作來\(Check\),確定\(m\)之后查詢區間前\(m\)大就可以計算出總共的\(\Delta\)。
那么該詢問的答案就是\(\frac{1}{2}(\sum_{i=1}^{n+1}|b_i|-\Delta)\)
時間復雜度\(O(n*log^2n)\)
代碼如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+10;
const int Inf=2147483647;
struct ELE{ int L,R,K,Id; };
int n,Qs,A[MAXN],B[MAXN];
ll Sum1,Ns,Sum[MAXN],Ans[MAXN];
vector<ELE>Q[MAXN];
int Read()
{ int a=0,c=1; char b=getchar();
while(b!='-'&&(b<'0'||b>'9')) b=getchar();
if(b=='-') c=-1,b=getchar();
while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
return a*c;
}
ll Abs(ll S){ return S>0?S:-S; }
int Min(int A,int B){ return A<B?A:B; }
namespace TREE
{ struct TR{ int C1,C2,Lson,Rson; ll S1,S2; }Tr[MAXN*40];
int Ts,Root[MAXN];
void Push_up(int S)
{ Tr[S].C1=Tr[Tr[S].Lson].C1+Tr[Tr[S].Rson].C1;
Tr[S].C2=Tr[Tr[S].Lson].C2+Tr[Tr[S].Rson].C2;
Tr[S].S1=Tr[Tr[S].Lson].S1+Tr[Tr[S].Rson].S1;
Tr[S].S2=Tr[Tr[S].Lson].S2+Tr[Tr[S].Rson].S2;
}
void Modify(int &S,int Le,int Ri,int D,int Aim,int K)
{ Tr[++Ts]=Tr[S],S=Ts;
if(Le==Ri) return K==1?(Tr[S].C1+=D,Tr[S].S1+=Le*D):(Tr[S].C2+=D,Tr[S].S2+=Le*D),(void)0;
int Mid=(1ll*Le+Ri)>>1;
return Aim<=Mid?Modify(Tr[S].Lson,Le,Mid,D,Aim,K):Modify(Tr[S].Rson,Mid+1,Ri,D,Aim,K),Push_up(S);
}
ll Query1(int Sl,int Sr,int Le,int Ri,int K)
{ if(Le==Ri) return 1ll*Le*Min(K,Tr[Sr].C1-Tr[Sl].C1);
if(Tr[Sr].C1-Tr[Sl].C1<=K) return Tr[Sr].S1-Tr[Sl].S1;
int Mid=(1ll*Le+Ri)>>1,Ll=Tr[Sl].Lson,Lr=Tr[Sl].Rson,Rl=Tr[Sr].Lson,Rr=Tr[Sr].Rson;
if(Tr[Rr].C1-Tr[Lr].C1>=K) return Query1(Lr,Rr,Mid+1,Ri,K);
return Query1(Ll,Rl,Le,Mid,K-Tr[Rr].C1+Tr[Lr].C1)+Tr[Rr].S1-Tr[Lr].S1;
}
ll Query2(int Sl,int Sr,int Le,int Ri,int K)
{ if(Le==Ri) return 1ll*Le*Min(K,Tr[Sr].C2-Tr[Sl].C2);
if(Tr[Sr].C2-Tr[Sl].C2<=K) return Tr[Sr].S2-Tr[Sl].S2;
int Mid=(1ll*Le+Ri)>>1,Ll=Tr[Sl].Lson,Lr=Tr[Sl].Rson,Rl=Tr[Sr].Lson,Rr=Tr[Sr].Rson;
if(Tr[Rr].C2-Tr[Lr].C2>=K) return Query2(Lr,Rr,Mid+1,Ri,K);
return Query2(Ll,Rl,Le,Mid,K-Tr[Rr].C2+Tr[Lr].C2)+Tr[Rr].S2-Tr[Lr].S2;
}
ll Query3(int Sl,int Sr,int Le,int Ri,int K)
{ if(Le==Ri) return K<=Tr[Sr].C1-Tr[Sl].C1?Le:-Inf;
int Mid=(1ll*Le+Ri)>>1,Ll=Tr[Sl].Lson,Lr=Tr[Sl].Rson,Rl=Tr[Sr].Lson,Rr=Tr[Sr].Rson;
if(Tr[Rr].C1-Tr[Lr].C1>=K) return Query3(Lr,Rr,Mid+1,Ri,K);
return Query3(Ll,Rl,Le,Mid,K-Tr[Rr].C1+Tr[Lr].C1);
}
ll Query4(int Sl,int Sr,int Le,int Ri,int K)
{ if(Le==Ri) return K<=Tr[Sr].C2-Tr[Sl].C2?Le:-Inf;
int Mid=(1ll*Le+Ri)>>1,Ll=Tr[Sl].Lson,Lr=Tr[Sl].Rson,Rl=Tr[Sr].Lson,Rr=Tr[Sr].Rson;
if(Tr[Rr].C2-Tr[Lr].C2>=K) return Query4(Lr,Rr,Mid+1,Ri,K);
return Query4(Ll,Rl,Le,Mid,K-Tr[Rr].C2+Tr[Lr].C2);
}
}using namespace TREE;
int main()
{ n=Read(),Qs=Read();
for(int i=1;i<=n;i++) A[i]=Read();
for(int i=1;i<=n+1;i++) B[i]=A[i]-A[i-1],Sum[i]=Sum[i-1]+Abs(B[i]);
for(int i=1,L,R,K;i<=Qs;i++) L=Read(),R=Read(),K=Read(),Q[R].push_back((ELE){L,R,K,i});
for(int i=1,L,R,K,Nr,Lt;i<=n+1;i++)
{ Root[i]=Root[i-1],Modify(Root[i],0,Inf,1,2*Abs(B[i]),B[i]<0?1:2);
Nr=Root[i],Lt=Ts;
for(ELE Nq:Q[i])
{ Sum1=0,L=Nq.L,R=Nq.R,K=Nq.K,Modify(Root[i],0,Inf,1,2*A[R],1);
Modify(Root[i],0,Inf,-1,2*Abs(B[L]),B[L]<0?1:2),Modify(Root[i],0,Inf,1,2*A[L],2);
for(int Le=1,Ri=n+1,Mid;Le<=Ri;)
{ Mid=(Le+Ri)>>1,Ns=Query3(Root[L-1],Root[R],0,Inf,Mid)+Query4(Root[L-1],Root[R],0,Inf,Mid);
if(Ns>2ll*K) Sum1=2ll*Mid*K-Query1(Root[L-1],Root[R],0,Inf,Mid)-Query2(Root[L-1],Root[R],0,Inf,Mid),Le=Mid+1;
else Ri=Mid-1;
}
Ans[Nq.Id]=(Sum[R]-Sum[L]+A[L]+Abs(A[R])+Sum1)/2,Root[i]=Nr,Ts=Lt;
}
}
for(int i=1;i<=Qs;i++) printf("%lld\n",Ans[i]);
}