【TJOI2019】甲苯先生的线段树(数位DP)

传送门


扯淡:

TJOI2019难一点的就只有这道D2T3了,前面五道我共计花了三个小时,这一道花了将近两个小时。五个小时AK两天TJOI不是梦

但是说实话这道题并没有什么水平,没考察什么思维方面的东西,涉及到的性质也极其偏门,天津就靠这个来选拔省队?怕不是反向选拔,反正天津今年也一块金牌也没有,这个锅省选和省选出题人肯定背定了。

然后我去题解区逛的时候还看到这居然是CF750G?而且还是没有什么改动的版本?天津省选出题人恐怕是在骗钱哦。所以唯一有“可能”有区分度的题就这样送给了做过这场CF比赛的人?

我记得某位前金牌OIer,现出题人说过:当我意识到我出的题可能会影响到别人的一生的时候,我只会过分慎重。

原话是什么我忘了,反正大概就是这个意思。

呵呵,这就是天津省选出题人的责任心?真是令人 [数据删除]

本人不在天津,以上发言利益无关。只是见到这么水的省选想把出题人拖出来按在地上爆锤而已。

为天津被这场板子题省选送退役的选手感到惋惜。

还说CQOI2018是板子题考试?起码该拿金牌的选手CQ确实选进省队了。

题解:

首先对于树上距离显然直接找到LCA。

考虑点 x x x到根的标号和,对于每一个位求一下和发现就是 2 ⋅ x − p c ( x ) 2\cdot x-pc(x) 2xpc(x),其中 p c ( x ) pc(x) pc(x) x x x的二进制表示中 1 1 1的个数。

第一问就此解决,设第一问答案为 s s s,现在考虑第二问。

现在需要考虑在不超过深度 d d d的点对中有多少对能够搞到 s s s

然后这道题就真的没什么意思了,尝试从各种角度枚举。

如果你运气足够好或者直觉足够准确,你会想到枚举左右链长度。

然后是一个更加扯淡的性质,确定了左右链长度之后,LCA就确定了。

L C A LCA LCA p p p,左链长度为 a a a,右链长度为 b b b,则我们把LCA单独的贡献算一下,是: ( p + 2 p + 4 p + ⋯ + 2 a p ) + ( p + 2 p + 4 p + ⋯ + 2 b p ) − p = ( 2 a + 1 + 2 b + 1 − 3 ) p (p+2p+4p+\cdots +2^ap)+(p+2p+4p+\cdots +2^bp)-p=(2^{a+1}+2^{b+1}-3)p (p+2p+4p++2ap)+(p+2p+4p++2bp)p=(2a+1+2b+13)p

然后考虑在定了 L C A LCA LCA和左右链长度之后,我们决定每一位走左右儿子能够得到的最大最小值,显然除了右链第一步必须走右儿子,左链第一步必须走左儿子以外,剩下的可以贪心向最大值最小值靠拢。那么根据走左右儿子变化的增量在 [ 2 b − 1 , 2 a + 2 b + 1 − a − b − 3 ] [2^b-1,2^a+2^{b+1}-a-b-3] [2b1,2a+2b+1ab3]之间。

那么就完蛋了,增量的最大值甚至比 2 a + 1 + 2 b + 1 − 3 2^{a+1}+2^{b+1}-3 2a+1+2b+13也就是前面 L C A LCA LCA贡献的系数还要小,那么 L C A LCA LCA p p p的情况的最大值比 p + 1 p+1 p+1的情况的最小值还小,也就是说在链长确定了之后,LCA确定的各个情况值域不相交。

所以如果要达到 s s s,LCA一定是确定的,就是 ⌊ s 2 a + 1 + 2 b + 1 − 3 ⌋ \lfloor\frac{s}{2^{a+1}+2^{b+1}-3}\rfloor 2a+1+2b+13s

这时候把LCA看做根,把 s s s减掉LCA的贡献和右链第一步走右儿子的贡献,设这个值为 w w w,那么我们就是要求有多少个合法的 x , y x,y x,y,使得 2 ∗ x + 2 ∗ y − p c ( x ) − p c ( y ) = w 2*x+2*y-pc(x)-pc(y)=w 2x+2ypc(x)pc(y)=w,枚举 p c ( x ) + p c ( y ) = z pc(x)+pc(y)=z pc(x)+pc(y)=z,则问题就是 2 x + 2 y = w + z 2x+2y=w+z 2x+2y=w+z,且 x , y x,y x,y二进制中 1 1 1的总数为 z z z x , y x,y x,y分别不超过自己链的上界。

显然这个东西可以直接一发数位DP搞定。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
#define pc __builtin_popcountll
using std::cerr;
using std::cout;

cs int N=55;
ll dp[2][N][N<<1];

inline ll solve(ll sum,ll cb,ll b1,ll b2){
	ll mxb=1;while((1ll<<mxb)<=sum)++mxb;
	for(int re i=0;i<=cb;++i)dp[0][0][i]=dp[1][0][i]=0;
	dp[0][0][0]=1;
	for(int re b=1;b<mxb;++b){
		sum>>=1;
		for(int re c=0;c<=cb;++c){
			dp[0][b][c]=dp[sum&1][b-1][c];
			dp[1][b][c]=0;
			if(c&&b<b1)dp[~sum&1][b][c]+=dp[~sum&1][b-1][c-1];
			if(c&&b<b2)dp[~sum&1][b][c]+=dp[~sum&1][b-1][c-1];
			if(c>1&&b<b1&&b<b2)dp[1][b][c]+=dp[sum&1][b-1][c-2];
		}
	}
	return dp[0][mxb-1][cb];
}

inline ll LCA(ll x,ll y){while(x!=y)(x<y?y:x)>>=1;return x;}
inline ll calc(ll x){return (x<<1)-pc(x);}
inline void solve(){
	int d,typ;ll x,y,p,ans=0;
	scanf("%d%lld%lld%d",&d,&x,&y,&typ);
	p=LCA(x,y);ll s=calc(x)+calc(y)-calc(p)-calc(p>>1);
	if(typ==1){cout<<s<<"\n";return ;}
	for(int re i=0;i<d;++i)
	for(int re j=0;j<d;++j){
		ll ss=(1ll<<i+1)+(1ll<<j+1)-3;if(s<ss)break;
		ll p=s/ss;int l=0;while(p){p>>=1;++l;}
		if(l+i>d||l+j>d)continue;
		ll k=s%ss-(1ll<<j)+1;
		if(k<0)continue;int cb=i+j;
		for(int re t=k&1;t<=cb;t+=2)
		ans+=solve(k+t,t,i,j);
	}
	cout<<ans-1<<"\n";
}

signed main(){
#ifdef zxyoi
	freopen("segment.in","r",stdin);
#endif
	int T;scanf("%d",&T);while(T--)solve();
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值