[线段树][数学] Jzoj P4237 Melancholy
题解
- 题目大意:有n个区间每个区间有一个相对距离D和矿值为V,每次询问相对距离在[l...r]中的选k个矿值的乘积之和(矿值最小的不选)
- K=1
- 显然,就是线段树区间查询就好了,就是V值之和-V值的最小值,只用维护区间和和区间最小值就好了
- K=2
- 貌似也不难,可以先计算没有减去最小V值的答案,那么只用记录一段区间和和最小值,然后先预处理出前缀和,然后根据这些其实就很容易推到答案
- K=3、4、5、6
- 本蒟蒻就不想推了,可以考虑也是线段树,维护一段区间取K个的乘积之和
- 那么考虑一下对于两个区间怎么去合并,那么如果现在两个区间中一共要取k个
- 那么是不是我们就要枚举两个区间内分别选多少个,然后可以将其乘起来p[i]=x[j]*y[i-j]/p[i]=x[j]*y[i-j-1]*y[7](代表的是当前的V值)
- 这样的话我们就解决了区间合并的问题,那么剩下的就是线段树的维护就好了
代码
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #include <cstring> 5 #define N 100010 6 using namespace std; 7 int n,Q,l,r,k; 8 unsigned int f[N*4][7],g[7],jc[7],p[7]; 9 struct edge{ int d,v; }a[N]; 10 bool cmp(edge a,edge b) { return a.d<b.d; } 11 void work(unsigned int*x,unsigned int*y,unsigned int*k) 12 { 13 if (x[7]>y[7]) swap(x,y); 14 if (!x[7]) for (int i=0;i<=7;i++) k[i]=y[i]; 15 else 16 { 17 memset(p,0,sizeof(p)),p[0]=1; 18 for (int i=1;i<=6;i++) 19 { 20 for (int j=0;j<=i;j++) p[i]+=x[j]*y[i-j]; 21 for (int j=0;j<i;j++) p[i]+=x[j]*y[i-j-1]*y[7]; 22 } 23 p[7]=x[7]; for (int i=0;i<=7;i++) k[i]=p[i]; 24 } 25 } 26 void build(int d,int l,int r) 27 { 28 f[d][0]=1; 29 if (l==r) { f[d][7]=a[l].v; return; } 30 int mid=l+r>>1; 31 build(d*2,l,mid),build(d*2+1,mid+1,r),work(f[d*2],f[d*2+1],f[d]); 32 } 33 void Query(int d,int l,int r,int L,int R) 34 { 35 if (a[l].d>R||a[r].d<L) return; 36 if (L<=a[l].d&&a[r].d<=R) { work(f[d],g,g); return; } 37 int mid=l+r>>1; 38 Query(d*2,l,mid,L,R),Query(d*2+1,mid+1,r,L,R); 39 } 40 int main() 41 { 42 scanf("%d%d",&n,&Q); 43 for (int i=1;i<=n;i++) scanf("%d",&a[i].d); 44 for (int i=1;i<=n;i++) scanf("%d",&a[i].v); 45 jc[0]=1; for (int i=1;i<=6;i++) jc[i]=jc[i-1]*i; 46 sort(a+1,a+n+1,cmp),build(1,1,n); 47 while (Q--) scanf("%d%d%d",&l,&r,&k),memset(g,0,sizeof(g)),g[0]=1,Query(1,1,n,l,r),printf("%lld\n",g[k]*jc[k]); 48 }