NOIP 2018模拟赛 by zwz T3 磨懒虫主义

磨懒虫主义


【题目简述】


DX3906星系,Melancholy星上,我在勘测这里的地质情况。


我把这些天来已探测到的区域分为N组,并用二元组(D,V)对每一组迚行标记:其中D为区域的相对距离,V为内部地质元素的相对丰富程度。


在我的日程安排表上有Q项指派的计划。每项计划的形式是类似的,都是“对相对距离D在[L,R]乊间的区域迚行迚一步的勘测,并在其中有次序地挑出K块区域的样本迚行研究。”采集这K块的样品后,接下来在实验中,它们的研究价值即为这K块区域地质相对丰富程度V的乘积。


我对这Q项计划都迚行了评估:一项计划的评估值P为所有可能选取情况的研究价值之和。


但是由于仪器的原因,在一次勘测中,这其中V最小的区域永进丌会被选取。


现在我只想知道这Q项计划的评估值对2^32取模后的值,特殊地,如果没有K块区域可供选择,评估值为0。


【输入格式】


第一行给出两个整数,区域数N和计划数Q


第二行给出N个整数,代表每一块区域的相对距离D。


第三行给出N个整数,代表每一块区域的内部地质元素的相对丰富程度V。


接下来的Q行,每一行3个整数,代表相对距离的限制L,R,以及选取的块数K。


【输出格式】


输出包括Q行,每一行一个整数,代表这项计划的评估值对2^32取模后的值。


【数据范围】


数据编号


数据约束


1,2,3
K=1
1<=N,Q<=10^5
1<=D,V<=10^9
1<=L<=R<=10^9
4,5,6
1<=K<=2
7,8
1<=K<=3
9,10
1<=K<=6
数据保证所有区域的D与V互不相等。
【样例输入输出】
样例输入
5 3


5 4 7 2 6


1 4 5 3 2


6 7 1


2 6 2


1 8 3

 

样例输出
5


52


924
【样例解释】
第一次被勘测区域的V值有{2,5},而能够被选取只有{5}。


第二次被勘测区域的V值有{1,2,3,4},能够被选取的有{2,3,4},评估值为2!*(2*3+3*4+2*4)=52。


第三次被勘测区域的V值有{1,2,3,4,5},能够被选取的有{2,3,4,5},评估值为3!*(2*3*4+2*3*5+2*4*5+3*4*5)=924。

 

题解:

考试时推了个表达式搞了60分,后来发现表达式推广一下可以拿到80分,可在往下推广就由于受到计算量的限制很难继续了。

随便聊聊吧。

首先我们要知道,这题n,q是没有梯度的,也就是说,O(nq)的算法基本第一个点都过不去

所以我们即使是暴力中也不能出现n(很显然,q是优化不掉的)

所以,我们的基本思想是把O(nq)优化成O(qlog2n),而能优化的部分有多少呢?

我们会发现,需要用到n的有以下几个地方:

第一,查出在区间[l,r]内的所有点

第二,找出这些点中v的最小值

第三,求出乘积

我们如果能优化上述三点,这道题就可以搞定

对于第一点,由于题目所有描述均与初始读入的顺序无关,所以我们可以将所有点按d从小到大排序,在查询时做两遍二分查找查出左右端点即可。

对于第二点,我们在一个序列中求区间最小值可以选择线段树或RMQ,由于这题不涉及修改,所以RMQ常数小,更快(当然,由于线段树也只有一个log2n,在前面已经存在一个二分查找的情况下,对复杂度的影响并不大,当然,常数两说)

ps:最近在刷线段树的题,看见这种东西实在是忍不住写线段树啊...RMQ神马的都忽略了...

真正考察算法的是第三点:

当k=1时,非常简单,只需用区间和减掉区间最小值即可,这就是30分了

当k=2时,需要进行一些推导:

设区间内所有元素为n1,n2...nm,最小值为nx

注意到他说顺序有影响,也就是说n1n2和n2n1是不同的方案,需要分别统计

那么结果就是:

n1(\sum ni-n1-nx)+n2(\sum ni-n2-nx)+...+nm(\sum ni-nm-nx)-nx(\sum ni-nx)

整理一下,也就是:(\sum ni-nx)^{2}-(\sum ni^{2}-nx^{2})(\sum ni-nx)^{2}-(\sum ni^{2}-nx^{2})

这两者都可以用前缀和维护,所以计算也是O(1)的

贴60分代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
#define mode (1ll<<32)
#define ls tree[rt].lson
#define rs tree[rt].rson
#define rt1 rt<<1
#define rt2 (rt<<1)|1
using namespace std;
struct Tree
{
  int lson;
  int rson;
  int minval;
}tree[400005];
ll s1[100005];
ll s2[100005];
struct node
{
  int d,v;
}p[100005];
bool cmp(node a,node b)
{
  return a.d<b.d;
}
void buildtree(int rt,int l,int r)
{
  ls=l;
  rs=r;
  if(l==r)
    {
      tree[rt].minval=p[l].v;
      return;
    }
  int mid=(l+r)>>1;
  buildtree(rt1,l,mid);
  buildtree(rt2,mid+1,r);
  tree[rt].minval=min(tree[rt1].minval,tree[rt2].minval);
}
int query(int rt,int l,int r)
{
  if(ls>r||rs<l)
    {
      return 0x3f3f3f3f;
    }
  if(ls>=l&&rs<=r)
    {
      return tree[rt].minval;
    }
  int mid=(ls+rs)>>1;
  if(r<=mid)
    {
      return query(rt1,l,r);
    }else if(l>mid)
    {
      return query(rt2,l,r);
    }else
    {
      return min(query(rt1,l,r),query(rt2,l,r));
    }
}
int n,q;
int find1(int val)
{
  int l=1,r=n;
  while(l<=r)
    {
      int mid=(l+r)>>1;
      if(p[mid].d<val)
	{
	  l=mid+1;
	}else
	{
	  r=mid-1;
	}
    }
  return r;
}
int find2(int val)
{
  int l=1,r=n;
  while(l<=r)
    {
      int mid=(l+r)>>1;
      if(p[mid].d<=val)
	{
	  l=mid+1;
	}else
	{
	  r=mid-1;
	}
    }
  return r;
}
int main()
{
  freopen("c.in","r",stdin);
  freopen("c.out","w",stdout);
  scanf("%d%d",&n,&q);
  for(int i=1;i<=n;i++)
    {
      scanf("%d",&p[i].d);
    }
  for(int i=1;i<=n;i++)
    {
      scanf("%d",&p[i].v);
    }
  sort(p+1,p+n+1,cmp);
  for(int i=1;i<=n;i++)
    {
      s1[i]=s1[i-1]+p[i].v;
      s1[i]%=mode;
      s2[i]=(ll)s2[i-1]+(ll)p[i].v*(ll)p[i].v%mode;
      s2[i]%=mode;
    }
  buildtree(1,1,n);
  for(int i=1;i<=q;i++)
    {
      int l,r,k;
      scanf("%d%d%d",&l,&r,&k);
      int lq=find1(l);
      int rq=find2(r);
      if(rq-lq<=k)
	{
	  printf("0\n");
	  continue;
	}else if(k==2)
	{
	  int t=query(1,lq+1,rq);
	  ll temp1=(s1[rq]-s1[lq]-t)*(s1[rq]-s1[lq]-t)%mode;
	  ll temp2=((s2[rq]-s2[lq]-t*t)%mode+mode)%mode;
	  printf("%I64d\n",((temp1-temp2)%mode+mode)%mode);
	}else 
	{
	  int t=query(1,lq+1,rq);
	  printf("%I64d\n",((s1[rq]-s1[lq]-t)%mode+mode)%mode);
	}
    }
  return 0;
}

当k=3,包括以上的时候,其实都可以以同样的方式处理,而且k=3时也并不是很难,结论是:

(\sum ni-nx)^{3}-2*(\sum ni^{2}-nx^{2})*(\sum ni-nx)-(\sum ni^{2}-nx^{2})+2*(\sum ni^{3}-nx^{3})

(天知道我是怎么推出这玩意的)

但是,k>=4的时候,问题会变得过于复杂以至于这种算法对计算量的要求过高,不适合再尝试

(什么叫计算量过高?我手动展开过(a+b+c)^5,但这个k=4的计算量已经很接近这个诡异的东西了,所以实在不适合人类食用...)

贴k=3的80分代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
#define mode (1ll<<32)
#define ls tree[rt].lson
#define rs tree[rt].rson
#define rt1 rt<<1
#define rt2 (rt<<1)|1
using namespace std;
struct Tree
{
  int lson;
  int rson;
  int minval;
}tree[400005];
ll s1[100005];
ll s2[100005];
ll s3[100005];
struct node
{
  int d,v;
}p[100005];
bool cmp(node a,node b)
{
  return a.d<b.d;
}
void buildtree(int rt,int l,int r)
{
  ls=l;
  rs=r;
  if(l==r)
    {
      tree[rt].minval=p[l].v;
      return;
    }
  int mid=(l+r)>>1;
  buildtree(rt1,l,mid);
  buildtree(rt2,mid+1,r);
  tree[rt].minval=min(tree[rt1].minval,tree[rt2].minval);
}
int query(int rt,int l,int r)
{
  if(ls>r||rs<l)
    {
      return 0x3f3f3f3f;
    }
  if(ls>=l&&rs<=r)
    {
      return tree[rt].minval;
    }
  int mid=(ls+rs)>>1;
  if(r<=mid)
    {
      return query(rt1,l,r);
    }else if(l>mid)
    {
      return query(rt2,l,r);
    }else
    {
      return min(query(rt1,l,r),query(rt2,l,r));
    }
}
int n,q;
int find1(int val)
{
	
  int l=1,r=n;
  while(l<=r)
    {
      int mid=(l+r)>>1;
      if(p[mid].d<val)
	{
	  l=mid+1;
	}else
	{
	  r=mid-1;
	}
    }
  return r;
}
int find2(int val)
{
  int l=1,r=n;
  while(l<=r)
    {
      int mid=(l+r)>>1;
      if(p[mid].d<=val)
	{
	  l=mid+1;
	}else
	{
	  r=mid-1;
	}
    }
  return r;
}
int main()
{
  freopen("c.in","r",stdin);
  freopen("c.out","w",stdout);
  scanf("%d%d",&n,&q);
  for(int i=1;i<=n;i++)
    {
      scanf("%d",&p[i].d);
    }
  for(int i=1;i<=n;i++)
    {
      scanf("%d",&p[i].v);
    }
  sort(p+1,p+n+1,cmp);
  for(int i=1;i<=n;i++)
    {
      s1[i]=s1[i-1]+p[i].v;
      s1[i]%=mode;
      s2[i]=(ll)s2[i-1]+(ll)p[i].v*(ll)p[i].v%mode;
      s2[i]%=mode;
      s3[i]=s3[i-1]+(ll)p[i].v*p[i].v%mode*p[i].v%mode;
      s3[i]%=mode;
    }
  buildtree(1,1,n);
  for(int i=1;i<=q;i++)
    {
      int l,r,k;
      scanf("%d%d%d",&l,&r,&k);
      int lq=find1(l);
      int rq=find2(r);
      if(rq-lq<=k)
	{
	  printf("0\n");
	  continue;
	}else if(k==2)
	{
	  int t=query(1,lq+1,rq);
	  ll temp1=(s1[rq]-s1[lq]-t)*(s1[rq]-s1[lq]-t)%mode;
	  ll temp2=((s2[rq]-s2[lq]-(ll)t*(ll)t%mode)%mode+mode)%mode;
	  printf("%I64d\n",((temp1-temp2)%mode+mode)%mode);
	}else if(k==3)
	{
		int t=query(1,lq+1,rq);
		ll temp1=(s1[rq]-s1[lq]-t)*(s1[rq]-s1[lq]-t)%mode*(s1[rq]-s1[lq]-t)%mode;
		ll temp2=2*(s2[rq]-s2[lq]-(ll)t*(ll)t%mode)%mode*(s1[rq]-s1[lq]-t)%mode;
		ll temp3=(s1[rq]-s1[lq]-t)*(s2[rq]-s2[lq]-(ll)t*(ll)t%mode)%mode;
		ll temp4=2*(s3[rq]-s3[lq]-(ll)t*(ll)t*(ll)t%mode);
		printf("%I64d\n",((temp1-temp2-temp3+temp4)%mode+mode)%mode);
	}else 
	{
	  int t=query(1,lq+1,rq);
	  printf("%I64d\n",((s1[rq]-s1[lq]-t)%mode+mode)%mode);
	}
    }
  return 0;
}

终于,我们说到正解了....

正解是倍增+容斥...

是的,不需要那堆数学公式...

我们会发现,我们是可以通过倍增预处理出以j为起点,长度为2^i的一段区间选k个所能获得的结果的(当然,不考虑去掉最小值的问题)

那么我们就先处理一下呗!

合并两组的时候用类似卷积的操作即可。

for(int i=1;i<=n;i++)
	{
		f[i][0][1]=p[i].v;
		f[i][0][0]=1;
	}
	int posi=0;
	for(int i=1;(1<<i)<=n;i++)
	{
		for(int j=1;j+(1<<(i-1))<=n+1;j++)
		{
			for(int t=0;t<=6;t++)
			{
				for(int y=0;y+t<=6;y++)
				{
					f[j][i][t+y]+=f[j][i-1][t]*f[j+(1<<(i-1))][i-1][y];
				}
			}
		}
		posi=i;
	}

接下来,我们在查询的时候就可以倍增求出这段区间的规划值(当然,也不考虑最小值),同时我们仍然用上面的方法求出最小值。

同样是一个类似卷积的过程

int l,r,k;
		scanf("%d%d%d",&l,&r,&k);
		int lq=find1(l);
		int rq=find2(r);
		if(rq-lq<k)
		{
			printf("0\n");
			continue;
		}
		uint t=query(1,lq+1,rq);
		int j=lq+1;
		int tot=posi;
		memset(ret,0,sizeof(ret));
		ret[0]=1;
		for(int r=posi;r>=0&&j<=rq;r--)
		{
			if((j+(1<<r))-1<=rq)
			{
				memset(temp,0,sizeof(temp));
				for(int tt=0;tt<=6;tt++)
				{
					for(int ty=0;ty+tt<=6;ty++)
					{
						temp[tt+ty]+=ret[tt]*f[j][r][ty];
					}
				}
				for(int tt=0;tt<=6;tt++)
				{
					ret[tt]=temp[tt];
				}
				j+=(1<<r);
			}
		}

所以我们接下来只需要去掉最小值的影响就可以了。

我们会发现,如果我们知道选k个,不包含最小值的规划值,同时我们知道,选k+1个包含最小值的规划值,那么我们就可以求出选k+1个时不包含最小值的规划值!

这是很显然的,因为把选k个的结果乘上最小值就是所有可能的选k+1个且包含了最小值的结果,那么我们用总的结果减去这个值就好啦

最后我们把结果乘一个k!(顺序考虑,所以要全排列)

就完事了。

贴代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define uint unsigned int
#define ls tree[rt].lson
#define rs tree[rt].rson
#define rt1 rt<<1
#define rt2 (rt<<1)|1
using namespace std;
struct Tree
{
  int lson;
  int rson;
  uint minval;
}tree[400005];
struct node
{
  int d;
  uint v;
}p[100005];
uint f[100005][32][7];
uint temp[7];
uint ret[7];
int n,m;
bool cmp(node a,node b)
{
  return a.d<b.d;
}
void buildtree(int rt,int l,int r)
{
  ls=l;
  rs=r;
  if(l==r)
    {
      tree[rt].minval=p[l].v;
      return;
    }
  int mid=(l+r)>>1;
  buildtree(rt1,l,mid);
  buildtree(rt2,mid+1,r);
  tree[rt].minval=min(tree[rt1].minval,tree[rt2].minval);
}
uint query(int rt,int l,int r)
{
  if(ls>r||rs<l)
    {
      return 0x3f3f3f3f;
    }
  if(ls>=l&&rs<=r)
    {
      return tree[rt].minval;
    }
  int mid=(ls+rs)>>1;
  if(r<=mid)
    {
      return query(rt1,l,r);
    }else if(l>mid)
    {
      return query(rt2,l,r);
    }else
    {
      return min(query(rt1,l,r),query(rt2,l,r));
    }
}
int find1(int val)
{
  int l=1,r=n;
  while(l<=r)
    {
      int mid=(l+r)>>1;
      if(p[mid].d<val)
	{
	  l=mid+1;
	}else
	{
	  r=mid-1;
	}
    }
  return r;
}
int find2(int val)
{
  int l=1,r=n;
  while(l<=r)
    {
      int mid=(l+r)>>1;
      if(p[mid].d<=val)
	{
	  l=mid+1;
	}else
	{
	  r=mid-1;
	}
    }
  return r;
}
int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&p[i].d);
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%u",&p[i].v);
	}
	sort(p+1,p+n+1,cmp);
	buildtree(1,1,n);
	for(int i=1;i<=n;i++)
	{
		f[i][0][1]=p[i].v;
		f[i][0][0]=1;
	}
	int posi=0;
	for(int i=1;(1<<i)<=n;i++)
	{
		for(int j=1;j+(1<<(i-1))<=n+1;j++)
		{
			for(int t=0;t<=6;t++)
			{
				for(int y=0;y+t<=6;y++)
				{
					f[j][i][t+y]+=f[j][i-1][t]*f[j+(1<<(i-1))][i-1][y];
				}
			}
		}
		posi=i;
	}
	for(int i=1;i<=m;i++)
	{
		int l,r,k;
		scanf("%d%d%d",&l,&r,&k);
		int lq=find1(l);
		int rq=find2(r);
		if(rq-lq<k)
		{
			printf("0\n");
			continue;
		}
		uint t=query(1,lq+1,rq);
		int j=lq+1;
		int tot=posi;
		memset(ret,0,sizeof(ret));
		ret[0]=1;
		for(int r=posi;r>=0&&j<=rq;r--)
		{
			if((j+(1<<r))-1<=rq)
			{
				memset(temp,0,sizeof(temp));
				for(int tt=0;tt<=6;tt++)
				{
					for(int ty=0;ty+tt<=6;ty++)
					{
						temp[tt+ty]+=ret[tt]*f[j][r][ty];
					}
				}
				for(int tt=0;tt<=6;tt++)
				{
					ret[tt]=temp[tt];
				}
				j+=(1<<r);
			}
		}
		uint whatever=1;
		for(int e=1;e<=k;e++)
		{
			whatever=ret[e]-whatever*t;
		}
		for(int e=1;e<=k;e++)
		{
			whatever*=e;
		}
		printf("%u\n",whatever);
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值