C.Delete Edges
題目鏈接
簡要題解
這是個構造題,官方給出的構造只有一種,而且證明過程很復雜。
先說結論:輸出所有滿足\(i+j+k=n\)和\(i+j+k=2*n\)的無序三元組\((i,j,k)\)
此處只證明該方案不會重復刪邊。
如果出現了重復刪邊的情況,那么必然是某兩個三元組內出現了兩個相同元素。
由於每個元素的大小不超過\(n\),那么確定了三元組的兩個元素之后我們可以唯一確定第三個元素。
所以在這個構造方案下,重復刪邊只可能是出現了兩個相同的三元組,這個情況我們完全可以避免,因此該方案不會重復刪邊。
代碼如下:
#include<bits/stdc++.h>
using namespace std;
struct Tri{ int A,B,C; }Ans[1000000];
int n,Cnt;
int main()
{ scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{ if(n-i-j>j) Ans[++Cnt]=(Tri){i,j,n-i-j};
if(2*n-i-j>j&&2*n-i-j<=n) Ans[++Cnt]=(Tri){i,j,2*n-i-j};
}
printf("%d\n",Cnt);
for(int i=1;i<=Cnt;i++) printf("%d %d %d\n",Ans[i].A,Ans[i].B,Ans[i].C);
}
D.Gambling Monster
題目鏈接
簡要題解
這是個期望題,我們不難想到期望\(Dp\)。
根據期望\(Dp\)的一般做法,我們可以設\(F[i]\)表示,當前狀態為\(i\),到達狀態\(n-1\)的期望步數。
初始狀態\(F[n-1]=0\)
接下來考慮轉移,下一步可能是原地踏步,也可能走到了更高的地方,並且花費了一步的代價:
改寫一下變成了
對於左邊,我們設\(G[i]=\sum_{v>i}P[v\bigoplus i]\),那么這個系數是確定的,我們可以想辦法提前算出來。
我們設\(w=v\bigoplus i\),那么考慮哪些\(i\)會使得\(w\bigoplus i>i\)。
由於涉及到大小比較,因此從高位開始考慮顯然更合理,因為高位對於大小的影響更大。
對於\(w\)最高位的\(1\)來說,如果\(i\)在該位為\(0\),那么異或的值肯定變大。如果\(i\)在該位為\(1\),那么異或的值肯定變小。
我們枚舉\(w\),得到\(w\)的最高位,對於所有在該位為\(0\)的\(i\),我們把\(G[i]\)加上\(P[w]\)。
暴力做是\(n^2\)的,不過我們可以利用差分將預處理的復雜度優化至\(O(nlogn)\)。
接下來考慮右邊,冷靜觀察就會發現,\((v\bigoplus i)\bigoplus v=i\),右邊是一個異或卷積的形式,我們可以利用\(FWT\)來加速。
對於\(i\)來說,我們需要所有大於它的數的貢獻,\(CDQ\)分治是處理這個問題的利器。
現在只剩下一個問題了:如何保證\(CDQ\)分治中\(FWT\)的復雜度?
\(FWT\)在卷積過程中信息和下標有關,我們一般不能直接改變下標,但是我們也不能每一層分治都做一遍\(nlogn\)的\(FWT\)。
實際上,對於一個區間內的所有數,二進制下只有最后若干位會發生變化。
同時由於本題\(n+1=2^x\),因此所有區間的長度都只包含質因子\(2\)。
我們完全可以把卷積數組的長度壓縮至區間長度,確定卷積下標與原數組下標的對應關系。
做完卷積之后根據下標的對應關系在相應位置加上貢獻即可。
時間復雜度\(O(n*log^2n)\)。
代碼如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=(1<<16)+10;
const int Inv2=500000004;
const int Mod=1e9+7;
int T,n,Sp,P[MAXN],F[MAXN],G[MAXN],R[MAXN];
int Log[MAXN],Pre[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 Pow(int Down,int Up)
{ int Ret=1,Now=Down;
while(Up>=1) Up&1?Ret=1ll*Ret*Now%Mod:0,Now=1ll*Now*Now%Mod,Up>>=1;
return Ret;
}
int Add(int A,int B){ return A+=B,A>=Mod?A-Mod:A; }
namespace Trans
{ int Len,A[MAXN],B[MAXN];
void FWT(int *P,int K)
{ for(int i=1;i<Len;i<<=1)
for(int Pos=i<<1,j=0;j<Len;j+=Pos)
for(int k=0;k<i;k++)
{ int X=P[j+k],Y=P[i+j+k];
P[j+k]=Add(X,Y),P[i+j+k]=Add(X,Mod-Y);
if(K==-1) P[j+k]=1ll*P[j+k]*Inv2%Mod,P[i+j+k]=1ll*P[i+j+k]*Inv2%Mod;
}
}
}using namespace Trans;
void Get_G()
{ for(int i=1;i<=n-1;i++)
for(int j=0;j<=n-1;j+=1<<Log[i]) Pre[j]=Add(Pre[j],P[i]),j+=1<<Log[i],Pre[j]=Add(Pre[j],Mod-P[i]);
G[0]=Pre[0];
for(int i=1;i<=n-1;i++) G[i]=Add(G[i-1],Pre[i]);
}
int Divide(int Le,int Ri,int K)
{ if(Le==Ri) return F[Le]=1ll*(R[Le]+1)*Pow(G[Le],Mod-2)%Mod;
int Mid=(Le+Ri)>>1;
Divide(Mid+1,Ri,K-1),Len=Mid-Le+1;
for(int i=Le;i<=Mid;i++) A[i-Le]=P[i-Le+Len],B[i-Le]=F[i+Len];
FWT(A,1),FWT(B,1);
for(int i=0;i<Len;i++) A[i]=1ll*A[i]*B[i]%Mod;
FWT(A,-1);
for(int i=Le;i<=Mid;i++) R[i]=Add(R[i],A[i-Le]);
return Divide(Le,Mid,K-1);
}
void Init()
{ for(int i=0;i<=n;i++) Pre[i]=G[i]=R[i]=F[i]=0;
Sp=0;
}
int main()
{ for(int i=2;i<=(1<<16);i++) Log[i]=Log[i>>1]+1;
for(T=Read();T;T--)
{ n=Read(),Init();
for(int i=0;i<=n-1;i++) P[i]=Read(),Sp+=P[i];
Sp=Pow(Sp,Mod-2);
for(int i=0;i<=n-1;i++) P[i]=1ll*P[i]*Sp%Mod;
Get_G(),Divide(0,n-1,Log[n]),printf("%d\n",F[0]);
}
}
J.Defend Your Country
題目鏈接
簡要題解
在賽場上,我們可以猜一個結論:
如果\(n\)為偶數,那么不需要操作。如果\(n\)為奇數,那么最優方案一定是只刪除一個點連接的所有邊,只有這個點的貢獻會變負。
我們把這個結論的證明放在最后,先說如何解題。
如果我們要刪點,自然想刪掉權值最小的那個點。
對於非割點來說,刪去不改變連通性,可以直接刪掉。
對於割點來說,可能與它連通的所有連通塊都包含了偶數個點,那么這個割點可以被刪掉,否則該割點不能被刪掉。
如何判斷這個割點是否可以被直接刪去呢?自然我們首先需要判斷哪些是割點。
利用\(Tarjan\)算法我們可以求出割點,並且在\(Tarjan\)的過程中我們會隨機跑出一棵生成樹,我們維護這棵生成樹的上所有點的子樹大小。
對於一個割點來說,它的一部分子樹和上面連通,還有一部分子樹和上面不連通。
對於和上面不連通的子樹來說,刪去割點之后這棵子樹就將獨立成一個連通塊,如果這棵子樹的大小為奇數,則該割點不能被刪掉。
對於和上面連通的子樹來說,我們不需要去管,因為刪去割點之后,所有連通塊大小的和為偶數,如果出現奇數個連通塊,必定至少出現兩個。
和上面連通的所有子樹,包括割點上面的一整塊,在割點被刪去后都在同一個連通塊內。
若這個連通塊大小為奇數,那么必定存在一個和上面不連通的子樹,其大小為奇數。
接下來是結論的證明:
反證法,假設最優方案是刪去多個點,那么一定是刪去奇數個割點。
如果刪去的點不是割點,只刪那一個非割點顯然更優。
假設我們有了原圖的點雙樹,再從點雙樹上把我們要刪除的割點拿出來做一棵虛樹。
由於我們只刪去了這些割點,那么刪去割點之后剩下的連通塊大小全部都是偶數。
那么如果我們只刪去虛樹上的一個葉子割點,它在點雙樹上的所有子樹大小都是偶數,則剩下的所有連通塊的大小也都是偶數。
這是一個合法方案,並且顯然只刪去這一個割點更優,因此假設不成立。
代碼如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+10;
const int Inf=2147483647;
struct EDGE{ int u,v,Next; }Edge[MAXN*4];
int T,n,m,Es,First[MAXN],Val[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; }
int Min(int A,int B){ return A<B?A:B; }
void Link(int u,int v){ Edge[++Es]=(EDGE){u,v,First[u]},First[u]=Es; }
namespace Tar
{ int Ds,Dfn[MAXN],Low[MAXN],Cut[MAXN],Size[MAXN],Good[MAXN];
void Tarjan(int Now,int Ba,int Cnt=0)
{ Dfn[Now]=Low[Now]=++Ds,Size[Now]=1;
for(int i=First[Now],v;i!=-1;i=Edge[i].Next)
if(Dfn[v=Edge[i].v]) Low[Now]=Min(Low[Now],Dfn[v]);
else
{ Tarjan(v,Ba),Size[Now]+=Size[v],Low[Now]=Min(Low[Now],Low[v]);
if(Size[v]%2&&Low[v]>=Dfn[Now]) Good[Now]=0;
Cut[Now]|=(Low[v]>=Dfn[Now]&&Now!=Ba),Cnt+=(Now==Ba);
}
Cut[Now]|=(Now==Ba&&Cnt>=2);
}
}using namespace Tar;
void Do()
{ int Nv=Inf;
for(int i=1;i<=n;i++)
if(!Cut[i]) Nv=Min(Nv,Val[i]);
else if(Good[i]) Nv=Min(Nv,Val[i]);
Ans-=2ll*Nv;
}
void Init()
{ for(int i=0;i<=n;i++) First[i]=-1,Cut[i]=Size[i]=Dfn[i]=Low[i]=0,Good[i]=1;
Ans=Es=Ds=0;
}
int main()
{ for(T=Read();T;T--)
{ n=Read(),m=Read(),Init();
for(int i=1;i<=n;i++) Val[i]=Read(),Ans+=Val[i];
for(int i=1,u,v;i<=m;i++) u=Read(),v=Read(),Link(u,v),Link(v,u);
if(n%2==0) printf("%lld\n",Ans);
else Tarjan(1,1),Do(),printf("%lld\n",Ans);
}
}