10.8 補坑

hzoi_zxb發表於2021-10-09

10.8 補坑

今天主要任務就是幹掉之前沒有做過去的模擬題,大概有個六七道,因為懶,所以不在向對應的場次總結裡放了。同時單獨拿出來也是意味著這些題的重要性和難度都是比較大的。

AVL 樹

優美的平衡樹,中序遍歷字典序最小,顯然是貪心。注意平衡樹的特殊性質,樹高是 log 級別的,也就是說可以暴力跳父親。

按照中序遍歷和先序遍歷貪心均可。
每一次檢查當前點是否可以加入最終的樹中,從當前點向上,每次遇到自己
是左子樹時,根據目前的情況計算右子樹至少需要留下多少的點。
計算得到一個點留下整棵樹至少多大,如果不超過K,則顯然可以。
由於讀入的樹是AVL樹,樹高為log?。
時間複雜度O(?????)。
根據目前情況,此處省略一萬字。。。。

現在來考慮如何根據目前情況,首先一棵深度為 i 的 AVL 節點數遞推為:
$$dp_i=dp_{i-1}+dp_{i-2}+1$$

然後貪心確定當前節點是否要留下,也就是去計算需要的節點是否小於等於 k 。考慮右子樹不僅要考慮當前點的深度,還要考慮之前選擇確定的深度,於是必須要記錄子樹內當前已經選了的最大深度。除此之外還需考慮這顆子樹的最大深度需求。

最大深度需求並不好搞,因為隨之影響的是整個子樹。考慮先序遍歷,因為兒子選了父親必須選,所以先序遍歷和中序遍歷是相同的,但是先序遍歷的好處是最大深度需求是可以隨之下放的!這樣就不用麻煩的標記了。

下放深度時如果左兒子深度夠用就把大的給左邊,否則給右邊。注意選點時要把對應的點刪掉。

#include<bits/stdc++.h>
#define N 500050
using namespace std;
int n,k,rt,fib[N],ch[N][2],fa[N],dep[N],mx[N],have[N],need[N];
bool ans[N];
inline void dfs(int x,int f)
{   dep[x]=dep[f]+1;mx[x]=dep[x];
    for(int i=0;i<=1;++i)if(ch[x][i])dfs(ch[x][i],x),mx[x]=max(mx[x],mx[ch[x][i]]);
}
inline bool check(int x)
{   int y=max(dep[x],have[x]),num=0;
    while(x)
    {   if(!ans[x])++num;
        y=max(y,have[x]);
        if(x==ch[fa[x]][0] and ch[fa[x]][1])num+=fib[max(y-1,need[ch[fa[x]][1]])-dep[fa[x]]];
        x=fa[x];
    }
    return num<=k;
}
inline void add(int x)
{   have[x]=max(have[x],dep[x]);int y=have[x];
    while(x)
    {   if(!ans[x])ans[x]=1,--k;
        have[x]=max(have[x],y);
        if(x==ch[fa[x]][0] and ch[fa[x]][1] and !ans[ch[fa[x]][1]])need[ch[fa[x]][1]]=max(need[ch[fa[x]][1]],have[x]-1);
        x=fa[x];
    }
}
inline void work(int x)
{   if(check(x))add(x);
    if(ch[x][0] and ch[x][1])
    {   if(mx[ch[x][0]]<need[x])
        {   need[ch[x][0]]=max(need[ch[x][0]],need[x]-1);
            need[ch[x][1]]=max(need[ch[x][1]],need[x]);
        }
        else
        {   need[ch[x][0]]=max(need[ch[x][0]],need[x]);
            need[ch[x][1]]=max(need[ch[x][1]],need[x]-1);
        }
    }
    else
    if(ch[x][0])
    need[ch[x][0]]=max(need[ch[x][0]],need[x]);
    else need[ch[x][1]]=max(need[ch[x][1]],need[x]);
    if(ch[x][0])work(ch[x][0]);
    if(ch[x][1])work(ch[x][1]);
}
signed main()
{   freopen("avl.in","r",stdin);
    freopen("avl.out","w",stdout);
    scanf("%d%d",&n,&k);
    fib[1]=1;for(int i=2;i<=n;++i)fib[i]=fib[i-1]+fib[i-2]+1;
    for(int i=1,x;i<=n;++i)
    {   scanf("%d",&x);
        if(x==-1)rt=i;
        else fa[i]=x,ch[x][x<i]=i;
    }
    dfs(rt,0);work(rt);
    for(int i=1;i<=n;++i)cout<<ans[i];cout<<endl;
}

石子游戲

對於x的答案,我們將每個數都mod (x+1),並求出抑或和。假如抑或和為
0,則假如Alice取某堆石子的數量超過了餘數,Bob可以取同一堆的石子使得餘
數變回去。如果Alice取的不超過某堆石子的餘數,那麼問題變為經典的NIM遊
戲,所以抑或和為0先手(Alice)必敗,否則必勝。
所以問題變為求所有可能的x對應的?"mod (x+1)的抑或和。
記?$表示?" = ?的數量。令y=x+1,考慮 $k≤\frac{n}{k}$,則對於屬於[ky,(k+1)y)的數字x,其mod y的結果就是 x-ky。我們考慮分別計算結果的每一個二進位制位 j,則我們需要知道,[ky,(k+1)y)
中,滿足t-ky包含j這個二進位制位的 $?_k$ 的和。 我們預處理f(i,j)表示對於所有的? ≥ ?,滿足x-i有j這個二進位制位的?$的和,那麼 $f_{i,j}=f_{i+2{j+1},j}+\sum_{k=i+2j}{i+2{j+1}-1}c_k $。通過對 f 的值的一些加減我們就可以得到區間[ky,(k+1)y)的答案。
時間複雜度 O(? log< ?)。

主要問題在於如何根據 f 得到答案,利用二進位制位於位之間獨立的性質,可以把詢問區間拆成整塊和散塊,整塊兩個相減,散塊用桶減。以上均利用了高位不影響低位的性質。

#include<bits/stdc++.h>
#define N 1000050
using namespace std;
int n,f[N<<1][21],c[N<<2],a[N],maxn;
signed main()
{   freopen("stone.in","r",stdin);
    freopen("stone.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d",&a[i]),maxn=max(maxn,a[i]),c[a[i]]++;
    for(int i=1;i<=(N<<1);++i)c[i]+=c[i-1];
    for(int i=maxn;i>=0;--i)for(int j=19;j>=0;--j)f[i][j]=f[i+(1<<(j+1))][j]+c[i+(1<<j+1)-1]-c[i+(1<<j)-1];
    for(int i=1;i<=n;++i)
    {   int k=n/(i+1),ans=0;
        for(int w=0;w<=19;++w)
        {   int tmp=0;
            for(int j=0;j<=k;++j)
            {   int l=j*(i+1),r=(j+1)*(i+1)-1;
                int len=(r-l+1)/(1<<w+1);
                if(!len)tmp+=max(0,c[r]-c[l+(1<<w)-1]);
                else tmp+=f[l][w]-f[l+len*(1<<w+1)][w]+max(0,c[r]-c[l+len*(1<<w+1)+(1<<w)-1]);
            }
            if(tmp&1){ans=1;break;}
        }
        if(ans)printf("Alice ");else printf("Bob ");
    }
}

記憶碎片

這題沒有想象的那麼難,dp 就是在列舉當前聯通塊的狀態,其實就是整數劃分。

加入一條邊的轉移非常暴力,如果是樹邊,暴力列舉合併兩個塊。否則乘上當前還剩下的內部邊。

#include<bits/stdc++.h>
#define mod 1000000007
#define N 40050
using namespace std;
int n,m,ans,dp[1001][N],tot,edge[N];
vector<int>cur,s[N],t,now;
map<vector<int>,int>mp;
bool vis[N];
inline void dfs(int x,int lim)
{   if(!lim)
    {   if(!x)
        {   s[++tot]=cur;mp[cur]=tot;
            for(auto i :cur)edge[tot]=(edge[tot]+i*(i-1)/2)%mod;
        }
        return;
    }
    dfs(x,lim-1);
    if(x>=lim)cur.push_back(lim),dfs(x-lim,lim),cur.pop_back();
}
signed main()
{   freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d",&n);m=(n-1)*n/2;
    for(int i=1,val;i<n;++i)scanf("%d",&val),vis[val]=1;
    dfs(n,n);dp[0][1]=1;
    for(int i=1;i<=m;++i)
    for(int j=1;j<=tot;++j)
    {   if(!dp[i-1][j])continue;
        int tmp=dp[i-1][j];
        if(vis[i])
        {   for(int u=0;u<s[j].size();++u)
            for(int v=u+1;v<s[j].size();++v)
            {   int newkuai=s[j][u]+s[j][v];
                now.clear();now.push_back(newkuai);
                for(int w=0;w<s[j].size();++w)if(w!=u and w!=v)now.push_back(s[j][w]);
                sort(now.begin(),now.end(),greater<int>());
                (dp[i][mp[now]]+=(1ll*s[j][u]*s[j][v]%mod*tmp%mod))%=mod;
            }
        }
        else
        dp[i][j]=(dp[i][j]+1ll*tmp*(edge[j]-(i-1))%mod)%mod;    
    }
    printf("%d\n",dp[m][tot]);
}

古老的序列問題

這種結構顯然是分治,離線詢問,將區間分成多個塊,在分治的每一層計算貢獻,分支的過程類似線段樹。統計貢獻用線段樹維護係數。

#include<bits/stdc++.h>
#define int long long
#define mod 1000000007
#define N 500050
#define inf 999999999999999999
using namespace std;
int n,seq[N],maxn[N],minn[N],c[N],d[N],f[N<<2],ans[N],q;
struct zxb{int l,r,id;bool friend operator <(zxb i,zxb j){return i.l>j.l;}};
vector<zxb>p[N<<2],v;
struct segment_Tree
{   int sum[N<<2],tag[N<<2],val[N<<2];
    inline void pushdown(int x)
    {   sum[x<<1]=(sum[x<<1]+val[x<<1]*tag[x]%mod)%mod;sum[x<<1|1]=(sum[x<<1|1]+val[x<<1|1]*tag[x]%mod)%mod;
        (tag[x<<1]+=tag[x])%=mod;(tag[x<<1|1]+=tag[x])%=mod;tag[x]=0;
    }
    inline void build(int x,int l,int r,int a[])
    {   sum[x]=tag[x]=0;
        if(l==r){val[x]=a[l];return;}
        int mid=(l+r)>>1;
        build(x<<1,l,mid,a);build(x<<1|1,mid+1,r,a);
        val[x]=(val[x<<1]+val[x<<1|1])%mod;
    }
    inline void ins(int x,int l,int r,int L,int R,int zhi)
    {   if(L>R)return;if(l>=L and r<=R){sum[x]=(sum[x]+val[x]*zhi%mod)%mod;tag[x]=(tag[x]+zhi)%mod;return;}
        int mid=(l+r)>>1;if(tag[x])pushdown(x);
        if(mid<R)ins(x<<1|1,mid+1,r,L,R,zhi);
        if(mid>=L)ins(x<<1,l,mid,L,R,zhi);
        sum[x]=(sum[x<<1]+sum[x<<1|1])%mod;
    }
    inline int query(int x,int l,int r,int L,int R)
    {   if(L>R)return 0;if(l>=L and r<=R)return sum[x];
        int mid=(l+r)>>1,res=0;if(tag[x]!=0)pushdown(x);
        if(mid<R)res=(res+query(x<<1|1,mid+1,r,L,R))%mod;
        if(mid>=L)res=(res+query(x<<1,l,mid,L,R))%mod;
        return res;
    }
}t1,t2,t3,t4;
inline int query(int l,int r,int L,int R)
{return (t1.query(1,l,r,L,R)+t2.query(1,l,r,L,R)+t3.query(1,l,r,L,R)+t4.query(1,l,r,L,R))%mod;}
inline void solve(int x,int l,int r)
{   if(l==r){f[x]=seq[l]*seq[r]%mod;for(auto i:p[x])ans[i.id]=(ans[i.id]+f[x])%mod;return;}
    int mid=(l+r)>>1;maxn[mid]=0;minn[mid]=inf;
    for(int i=mid+1;i<=r;++i)c[i]=1,maxn[i]=max(maxn[i-1],seq[i]),minn[i]=min(minn[i-1],seq[i]),d[i]=maxn[i]*minn[i]%mod;
    t1.build(1,mid+1,r,c);t2.build(1,mid+1,r,d);t3.build(1,mid+1,r,maxn);t4.build(1,mid+1,r,minn);
    v.clear();for(auto i:p[x])if(!(i.l==l and i.r==r) and i.l<=mid and i.r>mid)v.push_back(i);
    sort(v.begin(),v.end());int zhi1=mid,zhi2=mid,maxnl=0,minnl=inf;
    for(int i=mid,j=0;i>=l;--i)
    {   maxnl=max(maxnl,seq[i]);minnl=min(minnl,seq[i]);
        while(zhi1<r and maxn[zhi1+1]<maxnl)++zhi1;
        while(zhi2<r and minn[zhi2+1]>minnl)++zhi2;
        t1.ins(1,mid+1,r,mid+1,min(zhi1,zhi2),maxnl*minnl%mod);
        if(zhi1<zhi2)t3.ins(1,mid+1,r,zhi1+1,zhi2,minnl);
        if(zhi2<zhi1)t4.ins(1,mid+1,r,zhi2+1,zhi1,maxnl);
        t2.ins(1,mid+1,r,max(zhi1,zhi2)+1,r,1);
        while(j<v.size() and v[j].l==i)ans[v[j].id]=(ans[v[j].id]+query(mid+1,r,mid+1,v[j].r))%mod,++j;
    }
    for(auto i:p[x])
    {   if(i.l==l and i.r==r)continue;
        if(i.r<=mid)p[x<<1].push_back(i);else if(i.l>mid)p[x<<1|1].push_back(i);
        else {p[x<<1].push_back((zxb){i.l,mid,i.id});p[x<<1|1].push_back((zxb){mid+1,i.r,i.id});}
    }
    f[x]=(f[x]+query(mid+1,r,mid+1,r))%mod;solve(x<<1,l,mid);solve(x<<1|1,mid+1,r);f[x]=(f[x]+f[x<<1]+f[x<<1|1])%mod;
    for(auto i:p[x])if(i.l==l and r==i.r)ans[i.id]=(ans[i.id]+f[x])%mod;
}
signed main()
{   freopen("sequence.in","r",stdin);
    freopen("sequence.out","w",stdout);
    scanf("%lld%lld",&n,&q);
    for(int i=1;i<=n;++i)scanf("%lld",&seq[i]);
    for(int i=1,l,r;i<=q;++i)scanf("%lld%lld",&l,&r),p[1].push_back((zxb){l,r,i});
    solve(1,1,n);
    for(int i=1;i<=q;++i)printf("%lld\n",ans[i]);
}

假人

不妨先把下標變成從 0 開始編號。那麼每句假話的長度都在 [0, 4] 內。
考慮 dp(i, j) 表示前 i 組假話中各選一句假話,長度之和為 j 時的最大愉悅值。
這個dp 有如下凸性:dp(i, j + 12) −dp(i, j) ≥dp(i, j + 24) −dp(i, j + 12)。也就是說按模K = 12
的餘數來分組後,每組都是凸的。
簡要證明:對於任意一個滿足元素都在 [0, 4] 中、元素之和為 24 的(可重)集合 S,總能把 S
分成兩個和為 12 的集合(可以爆搜驗證)。於是可以直接得到上面這個結論。
那麼考慮分治,合併的時候就使用閔可夫斯基和的方法來合併,即 O(K2) 列舉兩邊分別屬於哪
個組,然後 O (nK) 歸併合併。這樣總的時間複雜度為 O(Kn log n)。

合併的過程注意對應的組別要進位,也就是組內位置+1。

#include<bits/stdc++.h>
#define int long long
#define N 100050
using namespace std;
int a[N][5],n,sum[N],tot,siz[N];
struct zxb{vector<int>p[12];};
inline void merge(const zxb &a,const zxb &b,zxb & c)
{   ++tot;
    for(int i=0;i<12;++i)if(a.p[i].size())
    for(int j=0;j<12;++j)if(b.p[j].size())
    {   int biao=(i+j)>=12,k=(i+j)%12,x=0,y=0;
        while(1)
        {   c.p[k][x+y+biao]=max(c.p[k][x+y+biao],a.p[i][x]+b.p[j][y]);
            if(x==a.p[i].size()-1 and y==b.p[j].size()-1)break;
            if(x==a.p[i].size()-1)++y;else if(y==b.p[j].size()-1)++x;
            else if(a.p[i][x+1]-a.p[i][x]>b.p[j][y+1]-b.p[j][y])++x;else ++y;
        }
    }
}
inline zxb solve(int l,int r)
{   zxb tmp;
    if(l==r)
    {   for(int i=0;i<siz[l];++i)tmp.p[i].push_back(a[l][i]);
        return tmp; 
    }
    int mid=(l+r)>>1;zxb a=solve(l,mid),b=solve(mid+1,r);
    for(int i=0;i<12;++i)for(int j=i;j<=sum[r]-sum[l-1];j+=12)tmp.p[i].push_back(-1);
    merge(a,b,tmp);return tmp;
}
signed main()
{   freopen("fake.in","r",stdin);
    freopen("fake.out","w",stdout);
    scanf("%lld",&n);
    for(int i=1;i<=n;++i)
    {   scanf("%lld",&siz[i]);a[i][0]=siz[i];
        for(int j=0;j<siz[i];++j)scanf("%lld",&a[i][j]);
        sum[i]=sum[i-1]+siz[i]-1;
    }
    zxb ans=solve(1,n);
    for(int i=0;i<=sum[n];++i)printf("%lld ",ans.p[i%12][i/12]);printf("\n");
}

相關文章