Link
记忆化搜索。
将搜索的状态设为((n,k,now,flg)),表示深度为(n),已有(k)位Prüfer Code,剩余(now)个需要被计算的位置,是否有父边。
假如当前的点为(x),那么我们将答案打成一个三元的结构体((a,b,c)),表示答案为(ax+b+clfloorfrac x2
floor)。
设当前点的答案为((a,b,c)),儿子的答案为((A,B,C)),考虑如何合并。
左儿子:(aleftarrow a+2A+C,bleftarrow b+B)。
右儿子:(aleftarrow a+2A+C,bleftarrow b+A+B)。
然后考虑利用记忆化优化时间复杂度。
首先只有当该子树对应的Prüfer Code编号在([a,a+(m−1)d]),且该子树有父边才进行记忆化。
我们设记忆的状态为(f_{k,i})表示根节点为(x),深度为(k),且任意(p′_{i+jd})都是需要被计算答案的点的答案为(f_{k,i})结构体所表示的答案((P'={p'_n})为子树的Prüfer Code)。
因为所有有父边深度为(k)的满二叉树的删除顺序是一定的,所以这一部分的答案只与该子树Prüfer Code中第一个被选取的点的offset有关。
我们只记录(kle 15)的状态,这样单次搜索的时间复杂度为(2^{frac k2})。
#include<cstdio>
#include<algorithm>
using i64=long long;
int read(){int x;scanf("%d",&x);return x;}
const int N=17,M=32768;
struct node
{
i64 a,b,c;
node(i64 A=0,i64 B=0,i64 C=0):a(A),b(B),c(C){};
void mergel(node x){a+=x.a+x.a+x.c,b+=x.b;}
void merger(node x){a+=x.a+x.a+x.c,b+=x.a+x.b;}
}f[N][M];
int st,d,m,ed,num,id[N][M];
node dfs(int n,int k,int now,int flg)
{
if(now<=0) return node();
int p=((st-k)%d+d)%d,size,pos;node ans;
if(!p) p=d;
p=std::max(p,st-k);
if(p>=1<<n) return node();
if(n<=1) return node(0,0,1);
if(n<=15&&flg&&k+1>=st&&k+(1<<n)-1<=ed&&id[n][p]==num) return f[n][p];
if(p<=(1<<(n-1))-1) size=(std::min(ed-k,(1<<(n-1))-1)-p)/d+1,ans.mergel(dfs(n-1,k,size,1)),now-=size;
if(!flg) if(pos=k+(1<<(n-1)),!((pos-st)%d)&&st<=pos&&pos<=ed) ans.a+=2,++ans.b,--now;
if(now>0) pos=k+(1<<n)-1,ans.merger(dfs(n-1,k+(1<<(n-1))-flg,now-(flg&&!((pos-st)%d)&&st<=pos&&pos<=ed),flg));
if(flg) if(pos=k+(1<<n)-1,!((pos-st)%d)&&st<=pos&&pos<=ed) ++ans.c;
if(n<=15&&flg&&k+1>=st&&k+(1<<n)-1<=ed) id[n][p]=num,f[n][p]=ans;
return ans;
}
int main()
{
for(int dep=read(),Q=read();Q;--Q)
{
++num,st=read(),d=read(),m=read(),ed=st+d*(m-1);
node ans=dfs(dep,0,m,0);printf("%lld
",ans.a+ans.b);
}
}