10.8 补坑

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-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");
}
posted @ 2021-10-09 06:26  -zxb-  阅读(120)  评论(0编辑  收藏  举报