IOI2021集训队作业 LC201 Cumulative Code

给出\(k\),记树高为\(k\)满二叉树(\(2^k-1\)个节点)的prufer序\(i\)个为\(p_i\),若干个询问,每次询问\(a,d,m\)表示求\(\sum_{i=0}^{m-1}p_{a+di}\)

\(k\le 30,Q\le 300\)

时间\(7s\)


做题的时候只枚举了\(k\le 4\)的prufer序,找到错误规律……

看到\(Q\le 300\)和那么多的时间,就应该发觉这不是数学题而是爆搜题。

首先分析一下这个prufer序如何构造:显然是对根的左子树进行后序遍历,往prufer序中输出遍历到的每个点的父亲,然后遍历根,然后递归到右子树的子问题。

定义\(F(x,k)\)\(G(x,k)\)分别表示根没有父亲和根有父亲的序列,序列中的每个元素用多项式表示。\(x\)表示根节点是什么。

那么有:\(F(x,k)=G(2x,k-1)+\{x\}+\{2x+1\}+F(2x+1,k-1)\)\(G(x,k)=G(2x,k-1)+\{x\}+G(2x+1,k-1)+\{x\}\)

如果没有推出通项,题目中的这条东西不太好求。于是可以考虑暴力,以\(2^b\)分一块计算。每一块都有询问\((a,d,m)\),在这块内计算出多项式之后用这一块的根节点的值代进去。

由于多项式是一样的,所以可以一起算,只算两种块:\(F(x,b)\)\(G(x,b)\)。可以\(O(2^bb)\)\(O(2^b)\)地求出块内每个数,然后\(O(2^{2b})\)地做个前缀和,这样每个块就可以\(O(1)\)询问了。询问的时候要拆成\(2^{k-b}\)个块。于是时间为\(O(2^{2b}+Q2^{k-b})\)

因为不想把空间开太大,所以我的程序中\(b\)取了\(10\)


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define B 10
#define ll long long
int k,b,Q;
struct poly{
	ll p,q;
	poly(ll _p=0,ll _q=0){
		p=_p,q=_q;
	}
	poly f(poly g){
		return poly(p*g.p,p*g.q+q);
	}
	ll f(ll x){
		return p*x+q;
	}
};
poly operator+(poly a,poly b){return poly(a.p+b.p,a.q+b.q);}
poly operator-(poly a,poly b){return poly(a.p-b.p,a.q-b.q);}
int len(int k){return (1<<k)-2;}
poly queryg(int x,int k){
	if (k==2){
		assert(x==1 || x==2);
		return poly(1,0);
	}
	if (x<=len(k-1)) return queryg(x,k-1).f(poly(2,0));
	if (x==len(k-1)+1) return poly(1,0);
	if (x<=len(k-1)+1+len(k-1)) return queryg(x-len(k-1)-1,k-1).f(poly(2,1));
	return poly(1,0);
}
poly queryf(int x,int k){
	if (k==2){
		assert(x==1);
		return poly(1,0);
	}
	if (x<=len(k-1)) return queryg(x,k-1).f(poly(2,0));
	if (x==len(k-1)+1) return poly(1,0);
	if (x==len(k-1)+2) return poly(2,1);
	return queryf(x-len(k-1)-2,k-1).f(poly(2,1));
}
struct data{
	poly h[1<<B],s[1<<B][1<<B];
	void init(poly query(int,int),int n){
		for (int i=1;i<=n;++i){
			h[i]=query(i,b);
	//		printf("(%lld,%lld)\n",h[i].p,h[i].q);
		}
	//	printf("\n");
		for (int j=1;j<n;++j){
			for (int i=n-j+1;i<=n;++i)
				s[j][i]=h[i];	
			for (int i=n-j;i>=1;--i)
				s[j][i]=s[j][i+j]+h[i];
	//		for (int i=1;i<=len(b);++i)
	//			printf("(%lld,%lld) ",s[j][i].p,s[j][i].q);
	//		printf("\n");
		}
	}
} F,G;
ll ans;
void calc(int c,int a,int d,int m,int t){
	if ((c-a)%d) return;
	int tmp=(c-a)/d;
	if (0<=tmp && tmp<=m)
		ans+=t;
}
void divideg(int k,int a,int d,int m,int t){
	if (k==b){
		ans+=(m==0?G.h[a]:(G.s[d][a]-(a+(m+1)*d<=len(b)?G.s[d][a+(m+1)*d]:poly(0,0)))).f(t);
		return;
	}
	calc(len(k-1)+1,a,d,m,t);
	calc(len(k-1)+1+len(k-1)+1,a,d,m,t);
	if (a+d*m==len(k-1)+1+len(k-1)+1){
		if (m==0) return;
		m--;
	}
	if (a<=len(k-1))
		divideg(k-1,a,d,min(m,(len(k-1)-a)/d),t*2);
	if (a+d*m>len(k-1)+1){
		int tmp=(a>len(k-1)+1?0:(len(k-1)+1-a+1 +d-1)/d);
		divideg(k-1,a+tmp*d-len(k-1)-1,d,m-tmp,t*2+1);
	}
}
void dividef(int k,int a,int d,int m,int t){
	if (k==b){
		ans+=(m==0?F.h[a]:(F.s[d][a]-(a+(m+1)*d<=len(b)?F.s[d][a+(m+1)*d]:poly(0,0)))).f(t);
		return;
	}
	calc(len(k-1)+1,a,d,m,t);
	calc(len(k-1)+2,a,d,m,t*2+1);
	if (a<=len(k-1))
		divideg(k-1,a,d,min(m,(len(k-1)-a)/d),t*2);
	if (a+m*d>len(k-1)+2){
		int tmp=(a>len(k-1)+1?0:(len(k-1)+2-a+1 +d-1)/d);
		dividef(k-1,a+tmp*d-len(k-1)-2,d,m-tmp,t*2+1);
	}
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d%d",&k,&Q);
	b=min(B,k);
	G.init(queryg,(1<<b)-2);
	F.init(queryf,(1<<b)-3);
//	for (int i=1;i<=(1<<k)-3;++i){
//		ans=0;
//		dividef(k,i,1,0,1);
//		printf("%lld\n",ans);
//	}
//	return 0;
	while (Q--){
		int a,d,m;
		scanf("%d%d%d",&a,&d,&m);
		assert(d);
		ans=0;
		dividef(k,a,d,m-1,1);
//		ll sum=0;
//		for (int i=0;i<m;++i)
//			sum+=queryf(a+d*i,k).f(1);
//		assert(ans==sum);
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2020-11-11 09:11  jz_597  阅读(122)  评论(0编辑  收藏  举报