2021牛客暑期多校訓練營6 部分題題解


C.Delete Edges

題目鏈接

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

題目鏈接

Gambling Monster

簡要題解

這是個期望題,我們不難想到期望\(Dp\)
根據期望\(Dp\)的一般做法,我們可以設\(F[i]\)表示,當前狀態為\(i\),到達狀態\(n-1\)的期望步數。
初始狀態\(F[n-1]=0\)
接下來考慮轉移,下一步可能是原地踏步,也可能走到了更高的地方,並且花費了一步的代價:

\[F[i]=\sum_{v\leq i}P[v\bigoplus i]*F[i]+\sum_{v>i}P[v\bigoplus i] *F[v]+1 \]

改寫一下變成了

\[\sum_{v>i}P[v\bigoplus i]F[i]=\sum_{v>i}P[v\bigoplus i] *F[v]+1 \]

對於左邊,我們設\(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

題目鏈接

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM