【性质分析+数位DP】CF750G New Year and Binary Tree Paths

规律找完,不会DP,光荣爆零,身败名裂

而且这还是道原题!!原题!!


从只往一棵子树走的情况考虑

\(x\)往左走了\(z\)个儿子,那总和为\(x\sum\limits_{i=0}^{z}2^i=x(2^{z+1}-1)\)

考虑从上到下第\(y(y\in(2,z])\)个儿子变成了右儿子,其它不变,则贡献为\(x\sum\limits_{i=0}^{z}2^i+\sum\limits_{i=0}^{z-y}2^i=x(2^z-1)+2^{z-y+1}-1\)

然后你稍微看一下会发现,当\(z\)固定时,\(x\)只能取\(\lfloor \frac{s}{2^z-1} \rfloor\),且只有一种走法

现在考虑\(x\)的左儿子向左下走了\(z1\)步,右儿子向左下走了\(z2\)步的情况

则有\(2x(2^{z1}-1)+(2x+1)(2^{z2}-1)+2^{z2}-1+x=(2^{z1+1}+2^{z2+1}-3)x+2^{z2}-1\)的贡献

然后枚举了\(z1\),\(z2\)后,\(x\)还是只有一个取值,即\(\lfloor \frac{s}{2^{z1+1}+2^{z2+1}-3} \rfloor\)

证明的话就考虑左子树和右子树走相同步,左子树最大的路径和一定小于右子树最小的路径和

然后问题转化成了:用\(\{1,3…,2^{h1}-1\}\)\(\{1,3,…,2^{h2}-1\}\)的元素组成\(s-\lfloor \frac{s}{2^{z1+1}+2^{z2+1}-3} \rfloor*(2^{z1+1}+2^{z2+1}-3)\)的方案数

\(s-\lfloor \frac{s}{2^{z1+1}+2^{z2+1}-3} \rfloor*(2^{z1+1}+2^{z2+1}-3)=res\)

这个\(-1\)很烦,于是你考虑枚举选了\(cnt\)个数,然后把所有\(2^i-1\)转化为\(2^i\),于是显然\(res+cnt\)为偶数时才有解

\(f[i][j][k]\)表示当前做到第\(i\)位,选了\(j\)个数,这位是否进位的方案数

对于\(2^j\),设第一个集合是否选这个数的状态为\(x(x\in\{0,1\})\),第二个集合的选择状态为\(y(y\in\{0,1\})\)

转移时,要满足\(x+y+k\)\(res+cnt\)的第\(j\)位奇偶性相同

于是有\(f[i+1][j+x+y][\frac{x+y+k}{2}]\leftarrow f[i][j][k]\)

然后就分类只走一边和两边的情况就行了

因为DRX太强了所以这里贴她的代码,我的代码在这里(大半年前写的,码风巨丑无比)

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i=j;i<=k;++i)
typedef long long ll;
typedef double db;
ll bit[65],f[65][120][2];//在{2,2^2,2^3...2^h1}∪{2,2^2,...2^h2}之间选tot个数,其和==s,                                                                    
inline ll dp(ll s,int tot,int h1,int h2){//应该是{2-1,2^2-1,2^3-1...2^h1-1}∪{2-1,2^2-1,...2^h2-1},但是已经把减一移过去s了
    memset(f,0,sizeof f);f[0][0][0]=1;//在{2,2^2...}中选,2^min(h1,h2)和之前的数可以选两个,f[i][j][k]表示做到第i个数,之前(包括i)选了j个数,对下一位的进位为k
    int ed=log2(s);//能选的最大的数 
    rep(i,1,ed){
        ll d=(s>>i)&1;
        int ed=i*2-2;//在 i之前最多能选多少个 
        rep(j,0,ed) rep(k,0,1)/*上一位是否进位*/rep(p1,0,1)/*左链是否向右走即是否选一个2^i*/rep(p2,0,1)/*右链同理*/if((i<h1||p1==0)&&(i<h2||p2==0)&& (p1+p2+k)%2==d)/*因为x的左右儿子即2^h1和2^h2是肯定要选的*/
        f[i][j+p1+p2][(k+p1+p2)/2]+=f[i-1][j][k]; 
    }
    return f[ed][tot][0];
} 
int main(){
    ll x,s,res,ans=0;
    int ed=0;
    scanf("%lld",&s);
    bit[0]=1;while(bit[ed]<=s) bit[++ed]=bit[ed-1]<<1;
    rep(i,1,ed){//统计一条链的情况 
        if(bit[i]-1>s) break;
        x=s%(bit[i]-1);
        for(int j=i;j>=1;--j) if(x>=bit[j]-1) x-=bit[j]-1;
        if(!x) ++ans;
    }
    rep(h1,1,ed-1){//统计有分叉的情况 
        for(int h2=1;bit[h2]-1<=s&&h2<ed;++h2){//枚举两边的链长 h1  h2 
            x=(s-bit[h2]+1)/(bit[h1+1]+bit[h2+1]-3);
            if(x>0){
                res=(s-bit[h2]+1)%(bit[h1+1]+bit[h2+1]-3);
                if(res==0){//不用向右走 
                    ++ans;continue;
                }
                if(h1==1&&h2==1){//x只选了左儿子x*2和右儿子x*2+1 ,所以是x*5+1 
                    ans+=(s==x*5+1);continue;
                }//有余数说明在某些点要往右走
                rep(i,1,h1+h2) if((res+i)%2==0) ans+=dp(res+i,i,h1,h2);//相当于把减掉的 i个一挪到等式另一边变成加 i个一 
            }
        }
    }
    printf("%lld",ans);
}

猜你喜欢

转载自www.cnblogs.com/PsychicBoom/p/11774327.html