B.Black and white
題目鏈接
簡要題解
我們不難發現,只需要對\(n+m-1\)個格子進行染色,就可以將整個棋盤染成黑色。
我們把當前的黑格子聚在一起,形成一個\(i\)行\(j\)列的矩陣,那么染一次色,就會使這個矩陣的行加一,或列加一。
初始我們染一次色,得到\(1\)行\(1\)列的矩陣,擴展到\(n\)行\(m\)列需要\(n+m-2\)次染色,因此總共是\(n+m-1\)次染色。
\(n+m-1\)這個數字其實可以認為是提示,因為\(n+m\)個點的樹就是\(n+m-1\)條邊。
我們把\(n\)行\(m\)列看作\(n+m\)個點,把\(i\)行\(j\)列的點染色,認為是在\(i\)行和\(j\)列對應點之間連了一條權值為\(c(i,j)\)的邊。
那么我們發現,我們利用魔法變黑的那些點,並不會影響到這張圖的連通性,花費代價染色的點,會合並兩個連通塊。
現在問題變得很清楚了,就是求着\(n+m\)個點的圖的最小生成樹。
由於點數少邊數多,\(Prim\)算法比較合適。
不過此題邊權較小,利用桶排實現的Kruskal算法也很優秀。
代碼如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5010;
struct VEC{ int H,L; };
int n,m,Cnt,Fa[MAXN*2];
ll Ans;
vector<VEC>Tong[100010];
void Input()
{ int A,B,C,D,P,Now;
scanf("%d%d%d%d%d",&A,&B,&C,&D,&P),Now=A;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
Now=(1ll*Now*Now*B+1ll*Now*C+D)%P,Tong[Now].push_back((VEC){i,j});
}
int Find(int S){ return Fa[S]==S?S:Fa[S]=Find(Fa[S]); }
int Merge(int A,int B){ return A=Find(A),B=Find(B),Fa[A]=B,A!=B; }
int main()
{ scanf("%d%d",&n,&m),Input();
for(int i=1;i<=n+m;i++) Fa[i]=i;
for(int i=0;i<=100000&&Cnt<n+m-1;i++)
{ for(VEC Pos:Tong[i])
if(Merge(Pos.H,Pos.L+n)) Cnt++,Ans+=i;
}
printf("%lld\n",Ans);
}
C
簡要題意
有一張\(n*n\)的網格圖,里面只有\(m\)個位置可以放非負整數。
現在已知每一行的最大值{\(b_i\)}和每一列的最大值{\(c_i\)},求所有合法方案中,網格圖內元素之和最小是多少。
數據范圍
\(1\leq n\leq 2000,1\leq m\leq 8*10^5,1\leq b_i,c_i \leq 10^6\)
簡要題解
我們已經知道每一行每一列的最大值,那么可以肯定的是,最優方案中的元素,要么是這些最大值之一,要么是0
不妨從大到小來考慮限制,因為我們很容易地能夠得到最大元素的限制。
我們找出當前限制的最大值,再找出有哪些行列的的最大值是當前最大,單獨考慮這些行列的交點。
那么我們必須選擇若干個交點放置元素,來滿足限制。一個交點可以滿足一行和一列的限制,我們的目標是使用最少的交點來滿足所有限制。
這就是個二分圖匹配問題了,我們把代表行的點放在左邊,代表列的點放在右邊,再把每個交點對應的行和列連邊,求最大匹配。
這個匹配的意義是:最多可以選擇這么多點,使得這些點能夠滿足一行和一列的限制。而其他點只能滿足一行或一列的限制。
那么需要的最少交點數就是:行數+列數-最大匹配數
我們把這些交點全部放上當前最大值,移除這個最大值,然后接着處理下一個最大值就行。
時間復雜度,大概是對\(4000\)個點求二分圖匹配的時間復雜度,還能跑
代碼如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2010;
struct LIM{ int Lim,P; }H[MAXN],L[MAXN];
bool Able[MAXN][MAXN];
int n,m,K,Hang[MAXN],Lie[MAXN];
ll Ans;
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; }
bool cmp(LIM A,LIM B){ return A.Lim<B.Lim; }
namespace Graph
{ vector<int>Edge[MAXN*2];
int Cp[MAXN*2],Vis[MAXN*2];
int Match(int Now,int Nc)
{ int Flag=0;
for(int v:Edge[Now])
{ Flag=0,Vis[v]==Nc?Flag=1:Vis[v]=Nc;
if(!Flag&&(!Cp[v]||Match(Cp[v],Nc))) return Cp[Now]=v,Cp[v]=Now,1;
}
return 0;
}
int Build(int Hs,int Ls)
{ int Ret=0;
for(int i=1;i<=Hs;i++) Edge[Hang[i]].clear();
for(int i=1;i<=Ls;i++) Edge[Lie[i]+n].clear();
for(int i=1;i<=Hs;i++)
for(int j=1,A,B;j<=Ls;j++)
{ if(Able[A=Hang[i]][B=Lie[j]]) Edge[A].push_back(B+n),Edge[B+n].push_back(A);
Cp[A]=Cp[B+n]=Vis[A]=Vis[B+n]=0;
}
for(int i=1;i<=Hs;i++) if(!Cp[Hang[i]]) Ret+=Match(Hang[i],Hang[i]);
return Hs+Ls-Ret;
}
}using namespace Graph;
void Solve()
{ int Th=n,Tl=n,Maxs,Nh,Nl;
while(Th>=1||Tl>=1)
{ Maxs=Max(H[Th].Lim,L[Tl].Lim),Nh=0,Nl=0;
while(Th>=1&&H[Th].Lim==Maxs) Hang[++Nh]=H[Th--].P;
while(Tl>=1&&L[Tl].Lim==Maxs) Lie[++Nl]=L[Tl--].P;
Ans+=1ll*Build(Nh,Nl)*Maxs;
}
}
int main()
{ n=Read(),m=Read(),K=Read();
for(int i=1;i<=n;i++) H[i].P=i,H[i].Lim=Read();
for(int i=1;i<=n;i++) L[i].P=i,L[i].Lim=Read();
for(int i=1,Nh,Nl;i<=m;i++) Nh=Read(),Nl=Read(),Able[Nh][Nl]=1;
sort(H+1,H+n+1,cmp),sort(L+1,L+n+1,cmp),Solve(),printf("%lld\n",Ans);
}
G.Yu Ling(Ling YueZheng) and Colorful Tree
題目鏈接
Yu Ling(Ling YueZheng) and Colorful Tree
簡要題解
官方題解是\(bitset\)和分塊,不過大部分人都沒有用這種寫法。
我們的限制條件有三個,第一個是祖先,第二個是顏色區間,第三個是顏色倍數。
其實前兩個限制比較常見,就是一個類似於二維數點的問題。祖先關系其實就是\(Dfn\)序上的一個區間,顏色也是一個區間,此處不展開敘述。
那么現在考慮如何處理第三個限制。
如果考慮所有操作,確實不太好想,但是如果我們每次只關心一部分操作,第三個限制就解決了。
具體地說,我們枚舉一個\(x\),每次把對\(x\)顏色的詢問和對\(x\)倍數的修改取出來,那么只有這里面的修改能滿足第三個條件,且所有的修改都滿足第三個條件,我們就在這些操作中解決詢問即可。
這樣的方法和虛樹類似,我們每次只關心一些關鍵點,因此能夠保證時間復雜度。
並且,題目保證每個點的顏色都不相同,這樣的話,雖然一個修改操作會被取出多次,但是所有操作取出數量之和為\(nlogn\)。
現在我們已經有了一個操作序列,並且沒有了第三個限制條件,解決方法就多了。
常見的有樹套樹,這里寫的是\(CDQ\)分治套主席樹。
我們利用\(CDQ\)分治來解決時間順序的影響,將操作序列按照執行時間排序,進行分治,每次只關心左邊的修改對右邊詢問的貢獻。
分治的每一層都建一棵主席樹,注意一定只考慮關鍵點,不然時間復雜度不對。
左邊的每一個操作,會影響\(Dfn\)上的一個區間,我們把這個作為主席樹根的下標,在左端點加上貢獻,右端點右邊減去貢獻。
對於每一個詢問,我們要求滿足條件的最近祖先,這個等價於求滿足條件的祖先中的\(Deep\)最大值。
所以主席樹維護顏色區間中的\(Deep\)最大值,在修改的顏色上加上修改點的\(Deep\)。
然后處理右邊的詢問,就是在詢問節點對應的主席樹上,進行區間最值查詢。
時間復雜度\(O(nlog^3n)\),操作數量一個\(log\),\(CDQ\)一個\(log\),主席樹一個\(log\),不過不滿,能跑過去。
代碼如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1.1e5+10;
struct EDGE{ int u,v,w,Next; }Edge[MAXN*2];
struct ELE{ int K,D,L,R,T,X; }Opt[MAXN];
int n,Os,Es,First[MAXN];
ll Ans[MAXN];
vector<int>Q[MAXN],M[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 Max(ll A,ll B){ return A>B?A:B; }
void Link(int u,int v,int w){ Edge[++Es]=(EDGE){u,v,w,First[u]},First[u]=Es; }
namespace PRE
{ int Ds,Dfn[MAXN],Size[MAXN],Fa[MAXN];
ll Deep[MAXN];
void Dfs(int Now,int Ba)
{ Dfn[Now]=++Ds,Size[Now]=1,Fa[Now]=Ba;
for(int i=First[Now],v;i!=-1;i=Edge[i].Next)
if((v=Edge[i].v)!=Ba) Deep[v]=Deep[Now]+Edge[i].w,Dfs(v,Now),Size[Now]+=Size[v];
}
}using namespace PRE;
namespace TREE
{ int Ts,Root[MAXN];
struct DOT{ int Lson,Rson; ll Maxs; }Tr[MAXN*20],Zero;
void Push_up(int S){ Tr[S].Maxs=Max(Tr[Tr[S].Lson].Maxs,Tr[Tr[S].Rson].Maxs); }
void Modify(int &S,int Le,int Ri,int Aim,ll Num)
{ Tr[++Ts]=Tr[S],S=Ts;
if(Le==Ri) return Tr[S].Maxs=Num,(void)0;
int Mid=(Le+Ri)>>1;
Aim<=Mid?Modify(Tr[S].Lson,Le,Mid,Aim,Num):Modify(Tr[S].Rson,Mid+1,Ri,Aim,Num),Push_up(S);
}
ll Query(int S,int Le,int Ri,int Al,int Ar)
{ if(Al<=Le&&Ri<=Ar) return Tr[S].Maxs;
int Mid=(Le+Ri)>>1;
return Max(Al<=Mid?Query(Tr[S].Lson,Le,Mid,Al,Ar):0,Mid<Ar?Query(Tr[S].Rson,Mid+1,Ri,Al,Ar):0);
}
}using namespace TREE;
namespace CDQ
{ struct Pair{ int Np; ll Dis; };
int Ss,Ld,Seq[MAXN*2],Dot[MAXN*4];
vector<Pair>Do[MAXN];
bool cmp(int A,int B){ return Opt[A].T<Opt[B].T; }
void CDQ(int Le,int Ri)
{ if(Le==Ri) return ;
int Mid=(Le+Ri)>>1; ELE Nd;
CDQ(Le,Mid),CDQ(Mid+1,Ri);
for(int i=Le;i<=Ri;i++)
if((Nd=Opt[Seq[i]]).K) Dot[++Ld]=Dfn[Nd.D];
else Dot[++Ld]=Dfn[Nd.D],Dot[++Ld]=Dfn[Nd.D]+Size[Nd.D];
sort(Dot+1,Dot+Ld+1),Ld=unique(Dot+1,Dot+Ld+1)-Dot-1;
for(int i=Le;i<=Mid;i++)
if(!(Nd=Opt[Seq[i]]).K)
Do[Dfn[Nd.D]].push_back((Pair){Nd.X,Deep[Nd.D]}),Do[Dfn[Nd.D]+Size[Nd.D]].push_back((Pair){Nd.X,0});
for(int i=1;i<=Ld;i++)
{ Root[Dot[i]]=Root[Dot[i-1]];
for(Pair j:Do[Dot[i]]) Modify(Root[Dot[i]],1,n,j.Np,j.Dis);
}
for(int i=Mid+1;i<=Ri;i++)
if((Nd=Opt[Seq[i]]).K) Ans[Nd.T]=Max(Ans[Nd.T],Query(Root[Dfn[Nd.D]],1,n,Nd.L,Nd.R));
for(int i=Le;i<=Mid;i++)
if(!(Nd=Opt[Seq[i]]).K) Do[Dfn[Nd.D]].clear(),Do[Dfn[Nd.D]+Size[Nd.D]].clear();
Ts=Ld=0;
}
void Solve()
{ for(int i=1;i<=Os;i++)
if(Opt[i].K) Q[Opt[i].X].push_back(i);
else M[Opt[i].X].push_back(i);
for(int i=1;i<=n;i++)
{ if(!Q[i].size()) continue ;
for(int j:Q[i]) Seq[++Ss]=j;
for(int j=i;j<=n;j+=i)
for(int k:M[j]) Seq[++Ss]=k;
sort(Seq+1,Seq+Ss+1,cmp);
CDQ(1,Ss),Ss=0;
}
}
}using CDQ::Solve;
int main()
{ n=Read(),Os=Read(),memset(First,-1,sizeof(First));
for(int i=1,u,v,w;i<n;i++) u=Read(),v=Read(),w=Read(),Link(u,v,w),Link(v,u,w);
for(int i=1;i<=Os;i++)
Opt[i].K=Read(),Opt[i].D=Read(),Opt[i].K==1?(Opt[i].L=Read(),Opt[i].R=Read()):0,Opt[i].X=Read(),Opt[i].T=i;
Deep[1]=1,Dfs(1,1),Solve();
for(int i=1;i<=Os;i++)
if(Opt[i].K) Ans[i]?printf("%lld\n",Deep[Opt[i].D]-Ans[i]):puts("Impossible!");
}
I
簡要題意
給定一個長度為n的序列\({a_i}\),有m次修改操作,求經過m次操作后的序列。
修改操作分為兩種:
1.給定\(l,r,x\),表示將\(l~r\)這一段的元素異或上x。
2.給定\(l,r,x\),表示將\(a_i\)異或上\(x+(i-l)\),其中\(l\leq i\leq r\)
數據范圍
\(1\leq n\leq 6*10^5,1\leq q\leq 4*10^5,0\leq a_i\leq2^{30}\)
簡要題解
對於第一種操作來說,我們可以直接利用差分來解決。
令$b_i=a_i\bigoplus a_{i-1} $ ,那么\(a_i=\bigoplus_{k=1}^ib_k\),對於每一個操作1來說,我們只需要將\(b_l\)和\(b_{r+1}\)異或上\(x\)即可。
對於第二種操作來說,我們一位位地來考慮。
假設當前計算第\(B\)位的貢獻,若第\(B\)位的值是1,則滿足$2^B\leq x+i-l(mod 2^{B+1}) $,移項得到\(2^B+l-x\leq i < 2^{B+1}+l-x(mod 2^{B+1})\)
我們把n個數按順序擺成\(2^{B+1}\)列的矩陣,即
容易發現,只有一半連續的列(模意義下最左邊和最右邊連續),異或上當前數會改變第\(B\)位的值,並且可以知道是哪些列。
我們已知當前需要修改的區間為\([l,r]\),那么把它對應到矩形中的區域,再和上述連續的列求交,會發現我們需要修改的是若干個矩形區域。
可以利用差分的技巧來完成快速修改,最后統計答案時做一個二維前綴和就行。
時間復雜度\(O(n*loga)\)
代碼如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=6e5+10;
struct IN{ int Le,Ri,X; }Opt[MAXN];
int n,Qs,Os,A[MAXN],Hs[32],Ls[32];
ll Two[32];
vector<bool>M[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 Min(ll A,ll B){ return A<B?A:B; }
ll Max(ll A,ll B){ return A>B?A:B; }
void Modify(int H1,int L1,int H2,int L2)
{ M[H1][L1]=M[H1][L1]^1,M[H2+1][L1]=M[H2+1][L1]^1,M[H1][L2+1]=M[H1][L2+1]^1,M[H2+1][L2+1]=M[H2+1][L2+1]^1; }
void Solve(int L,int R,int Minh,int K,int Nb)
{ int St=(Opt[K].Le-1)%Two[Nb+1]+1,End=(Opt[K].Ri-1)%Two[Nb+1]+1;
if(L<=R)
{ St=Max(St,L),End=Min(End,R);
if(St<=End) Modify(Minh,St,Minh,End);
}
if(L>R)
{ if(St<=R) Modify(Minh,St,Minh,Min(R,End));
if(End>=L) Modify(Minh,Max(St,L),Minh,End);
}
}
void Solve1(int L,int R,int Minh,int K,int Nb)
{ int St=(Opt[K].Le-1)%Two[Nb+1]+1;
if(L<=R&&St<=R) St=Max(St,L),Modify(Minh,St,Minh,R);
if(L>R)
{ if(St<=R) Modify(Minh,St,Minh,R);
St=Max(St,L),M[Minh][St]=M[Minh][St]^1,M[Minh+1][St]=M[Minh+1][St]^1;
}
}
void Solve2(int L,int R,int Minh,int Maxh,int K,int Nb)
{ if(Minh>Maxh) return ;
if(L<=R) Modify(Minh,L,Maxh,R);
else Modify(Minh,1,Maxh,R),M[Minh][L]=M[Minh][L]^1,M[Maxh+1][L]=M[Maxh+1][L]^1;
}
void Solve3(int L,int R,int Maxh,int K,int Nb)
{ int End=(Opt[K].Ri-1)%Two[Nb+1]+1;
if(L<=R&&End>=L) End=Min(End,R),Modify(Maxh,L,Maxh,End);
if(L>R)
{ if(End>=L) Modify(Maxh,L,Maxh,End);
End=Min(End,R),Modify(Maxh,1,Maxh,End);
}
}
int main()
{ n=Read(),Qs=Read(),Two[31]=1ll<<31;
for(int i=0;i<=30;i++) Two[i]=1<<i,Ls[i]=Min(n,1ll<<(i+1)),Hs[i]=(n-1)/Ls[i]+1;
for(int i=1;i<=n;i++) A[i]=Read();
for(int i=n;i>=1;i--) A[i]^=A[i-1];
for(int i=1,K,Le,Ri,X;i<=Qs;i++)
{ K=Read(),Le=Read(),Ri=Read(),X=Read();
if(K==0) A[Le]^=X,A[Ri+1]^=X;
else Opt[++Os]=(IN){Le,Ri,X};
}
for(int Nb=0;Nb<=30;Nb++)
{ for(int i=0;i<=Hs[Nb]+1;i++) M[i].clear();
for(int i=0;i<=Hs[Nb]+1;i++)
for(int j=0;j<=Ls[Nb]+1;j++) M[i].push_back(0);
for(int i=1;i<=Os;i++)
{ ll L=(Two[Nb]+Opt[i].Le-Opt[i].X%Two[Nb+1]+Two[Nb+1]-1)%Two[Nb+1]+1;
ll R=(Two[Nb+1]+Opt[i].Le-Opt[i].X%Two[Nb+1]-2)%Two[Nb+1]+1;
int Minh=(Opt[i].Le-1)/Two[Nb+1]+1,Maxh=(Opt[i].Ri-1)/Two[Nb+1]+1;
if(L>n&&L<=R) continue ;
if(L<=R&&R>n) R=n;
if(L>R&&L>n) L=1;
if(L>R&&R>n) R=n;
if(Minh==Maxh) Solve(L,R,Minh,i,Nb);
else Solve1(L,R,Minh,i,Nb),Solve2(L,R,Minh+1,Maxh-1,i,Nb),Solve3(L,R,Maxh,i,Nb);
}
for(int i=1,Cnt=0;i<=Hs[Nb];i++)
for(int j=1;j<=Ls[Nb];j++)
M[i][j]=M[i][j]^M[i][j-1]^M[i-1][j]^M[i-1][j-1],Cnt++,A[Cnt]^=M[i][j]<<Nb,A[Cnt+1]^=M[i][j]<<Nb;
}
for(int i=1;i<=n;i++) A[i]^=A[i-1],printf("%d ",A[i]);
}