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;
}