【Codeforces 750G】—New Year and Binary Tree Paths(数位dp)

传送门

题意:给出一颗无限大的线段树,询问有多少条路径满足点的编号和为 k , k ≤ 1 0 15 k,k\le10^{15} k,k1015

实际上线段树只有 50 50 50层左右
考虑一条路径就是2条向下延伸的链

考虑一条链的情况
实际上如果确定了链的长度 l l l,就可以唯一确定一个出发点 x x x
证明:
如果链一直向左儿子走,编号和显然为 ( 2 l + 1 − 1 ) x (2^{l+1}-1)x (2l+11)x
对于 x − 1 x-1 x1,如果一直向右儿子走
那么编号和为
( 2 l + 1 − 1 ) ( x − 1 ) + ∑ i = 1 l − l ( 2 i − 1 ) = ( 2 l + 1 − 1 ) x − l &lt; ( 2 l + 1 − 1 ) x (2^{l+1}-1)(x-1)+\sum_{i=1}^{l-l}(2^{i}-1)=(2^{l+1}-1)x-l&lt;(2^{l+1}-1)x (2l+11)(x1)+i=1ll(2i1)=(2l+11)xl<(2l+11)x

所以 x x x一定等于 ⌊ k 2 l + 1 − 1 ⌋ \lfloor\frac{k}{2^{l+1}-1}\rfloor 2l+11k
考虑在从下往上第 i i i个位置如果向右儿子走,贡献是 2 i − 1 2^i-1 2i1
r e s = k − ( 2 l + 1 − 1 ) x res=k-(2^{l+1}-1)x res=k(2l+11)x
我们就只需要看 r e s res res能否被 2 1 − 1 , 2 2 − 1..... 2 l − 1 2^1-1,2^2-1.....2^l-1 211,221.....2l1表示出来
暴力从高往低做就完了

考虑2条链的情况
设左右链的长度为 l , r l,r l,r
那么可以得到贡献为 ( 2 l + 1 + 2 r + 1 − 3 ) x + 2 r − 1 (2^{l+1}+2^{r+1}-3)x+2^r-1 (2l+1+2r+13)x+2r1
类似的可以证明 x x x唯一
即可得到 r e s res res
考虑现在就是用 2 1 − 1 , 2 2 − 1 , . . . . . , 2 l − 1 − 1 , 2 1 − 1 , 2 2 − 1.... 2 r − 1 − 1 2^1-1,2^2-1,.....,2^{l-1}-1,2^1-1,2^2-1....2^{r-1}-1 211,221,.....,2l11,211,221....2r11
凑出 r e s res res来(因为第一个左右确定了)

由于有个 − 1 -1 1,考虑枚举一共选了几个数 k k k
看能不能凑出 r e s + k res+k res+k
f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]表示前 i i i位,已经选了 j j j个,是否有进位
每次就枚举 l , r l,r l,r这一位分别选不选
d p dp dp就完了

复杂度 O ( l o g 5 ) O(log^5) O(log5)

#include<bits/stdc++.h>
using namespace std;
const int RLEN=1<<20|1;
inline char gc(){
    static char ibuf[RLEN],*ib,*ob;
    (ob==ib)&&(ob=(ib=ibuf)+fread(ibuf,1,RLEN,stdin));
    return (ob==ib)?EOF:*ib++;
}
#define gc getchar
inline int read(){
    char ch=gc();
    int res=0,f=1;
    while(!isdigit(ch))f^=ch=='-',ch=gc();
    while(isdigit(ch))res=(res+(res<<2)<<1)+(ch^48),ch=gc();
    return f?res:-res;
}
#define ll long long
inline ll readl(){
    char ch=gc();
    ll res=0,f=1;
    while(!isdigit(ch))f^=ch=='-',ch=gc();
    while(isdigit(ch))res=(res+(res<<2)<<1)+(ch^48),ch=gc();
    return f?res:-res;
}
#define re register
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define cs const
#define bg begin
const int mod=998244353;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline void Add(int &a,int b){a=add(a,b);}
inline int dec(int a,int b){return a>=b?a-b:a-b+mod;}
inline void Dec(int &a,int b){a=dec(a,b);}
inline int mul(int a,int b){return 1ll*a*b>=mod?1ll*a*b%mod:a*b;}
inline void Mul(int &a,int b){a=mul(a,b);}
inline int ksm(int a,int b,int res=1){for(;b;b>>=1,a=mul(a,a))(b&1)?(res=mul(res,a)):0;return res;}
inline int Inv(int x){return ksm(x,mod-2);}
inline void chemx(int &a,int b){a<b?a=b:0;}
inline void chemn(int &a,int b){a>b?a=b:0;}
cs int N=57;
ll bin[N];
inline ll solve1(ll x,ll y){
	ll res=0;
	while(x!=y){
		if(x<y)swap(x,y);
		res+=x,x>>=1;
	}
	res+=x;return res;
}
ll f[N][N*2][2];
inline ll dp(ll mx,int l,int r,int t){
	memset(f,0,sizeof(f));
	f[0][0][0]=1;int len=1;
	for(int i=1;bin[i-1]<=mx;i++,len++){
		int d=(mx>>i)&1;
		for(int j=0;j<=t;j++){
			for(int x=0;x<=1;x++)
			if(x!=1||i<l)
			for(int y=0;y<=1;y++)
			if(y!=1||i<r){
				if((x+y)%2==d)f[i][j+x+y][(x+y)/2]+=f[i-1][j][0];
				else f[i][j+x+y][(x+y+1)/2]+=f[i-1][j][1];
			}
		}
	}
	return f[len-1][t][0];
}
inline ll solve2(ll s){
	ll ans=0;
	for(int i=0;bin[i]<=s;i++)
	for(int j=0;bin[j]<=s;j++){
		ll x=(s-bin[j]+1)/(bin[i+1]+bin[j+1]-3);
		if(!x)continue;
		ll res=s-bin[j]+1-(bin[i+1]+bin[j+1]-3)*x;
		if(!res){ans++;continue;}
		for(int k=1;k<=i+j-(i!=0)-(j!=0);k++)
		if((res+k)%2==0)ans+=dp(res+k,i,j,k);
	}
	return ans;
}
inline void solve(){
	ll d=readl();
	cout<<solve2(d)<<'\n';
}
int main(){
	for(int i=0;i<N;i++)bin[i]=1ll<<i;
	solve();
}
区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值