磨懒虫主义

【题目简述】

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是不同的方案,需要分别统计

那么结果就是:

整理一下,也就是:

这两者都可以用前缀和维护,所以计算也是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时也并不是很难,结论是:

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

但是,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;
}

NOIP 2018模拟赛 by zwz T3 磨懒虫主义相关推荐

  1. noip 2018 模拟赛16

    T1T_1T1​--gxc(3830) Description: 有一个长度为nnn的序列AAA,假如有将该序列划分成mmm个区间,满足每个区间排序后序列AAA是有序的,求最大的mmm. n≤2000 ...

  2. NOIP 2018模拟赛(模拟) 2018 11 3 A组 Day1 T1

    T1 铃仙的红色之瞳(eyes) 题目描述 为了方便你的预测,铃仙对该符卡进行了改造. 敌方非常强大,可以看作有无限的体力.通过该符卡,铃仙可以释放出子弹,敌方触碰到子弹就会损失一格体力.注意,每次敌 ...

  3. 【NOIP考前模拟赛】纯数学方法推导——旅行者问题

    一.写在前面 这题似乎是一道原创题目(不是博主原创),所以并不能在任何OJ上评测,博主在网盘上上传了数据(网盘地址:http://pan.baidu.com/s/1mibdMXi),诸位看官需者自取. ...

  4. [NowCoder牛客]2021NOIP提高组模拟赛第二场T3——树数树(启发式合并堆)

    树数树 description solution code description [题目描述] 牛牛有一棵 n 个点的有根树,根为 1. 我们称一个长度为 m 的序列 a 是好的,当且仅当: • ∀

  5. NOIP复习模拟赛day1

    首先先bb一下day1的题目名字怎么都这么鬼才......(连起来叫两情若是长久时) 1.两情 (sweethearts.pas/c/cpp) [问题描述]小 W 将要去和小 K 约会啦!但聪(ao) ...

  6. JZOJ(中山纪念中学) 2018.02.02【NOIP普及组】模拟赛D组

    本次题目:2018.02.02[NOIP普及组]模拟赛D组 第一题 题目:第一题 公牛数字 题意: 求题目给出两个数字的乘积 分析: 这题明显只是考察学生的高精可我居然没做对,只要多练习几次,即可AC ...

  7. 【HHHOJ】NOIP模拟赛 捌 解题报告

    点此进入比赛 得分: \(30+30+70=130\)(弱爆了) 排名: \(Rank\ 22\) \(Rating\):\(-31\) \(T1\):[HHHOJ260]「NOIP模拟赛 捌」Dig ...

  8. 2018.10.9模拟赛

    2018.10.9模拟赛 T1 trade 正解:贪心 据说lyd讲过但并没有印象QAQ,考场上现推浪费了不少时间 其实就开个小根堆,每次把堆顶取出来看它是不是比当前的 a[i]a[i]a[i] 小, ...

  9. NOIP模拟赛csy2021/10/30

    NOIP模拟赛csy2021/10/30 比赛时间规划 赛后反思与总结 这..总的来说感觉打的很不好,根本没有状态,有一部分原因是今天来晚了,太慌,更多的还是这次题感觉很难o(╥﹏╥)o 比赛时间规划 ...

  10. {小结}2016.6.11【初中部 NOIP提高组 】模拟赛C

    2016.6.11[初中部 NOIP提高组 ]模拟赛C No.1!!! 100+33.3+10+90=233.3 23333 1298. 牛棚(graze2.pas/c/cpp) 题解 1299. 洗 ...

最新文章

  1. 机器学习PAI产品架构
  2. arial unicode ms字体_5个检测商用字体和免费字体合集的网站
  3. 并发编程-18AQS同步组件之 CyclicBarrier 同步屏障
  4. Service Fabric下删除实例并注销应用
  5. php mysql连续签到跨月_PHP连续签到功能实现方法详解
  6. 让 Python 更加充分的使用 Sqlite3
  7. 深入浅出Docker 镜像 | 技术头条
  8. oracle 9 插入日期,oracle date日期类型 精析
  9. 【JavaScript算法】---快速排序法
  10. Node.js Server
  11. hashmap是线程安全的吗?怎么解决?_线程安全及三种解决方案
  12. Timus 1741
  13. 树莓派舵机 c语言,树莓派控制SG90舵机
  14. 如何制作微软原版Win10安装盘
  15. 你绝对不知道 Vue 也有生老病死
  16. 滴滴打车2015-2016
  17. 【编译问题】海思3559A编译问题 texi2dvi:命令找不到
  18. 在线协作文档综合评测 :金山文档、腾讯文档、石墨文档
  19. 足不出户也能放风筝?OpenGL 一招搞定!
  20. Linux常用命令——hostid命令

热门文章

  1. 程序性能优化之编译器篇(Racoon)
  2. G1 Concurrent Refinement Thread 在干啥?
  3. Multiple Instance Detection Network with Online Instance Classifier Refinement
  4. 软件测试之App测试-硬件环境测试
  5. [CF106C]Buns -多重背包
  6. 中国大学MOOC测验爬取(下)
  7. CMS内容管理系统(含小程序,Uni APP) 搭建
  8. Hystrix使用分析
  9. R语言sparse.model.matrix函数报错
  10. MATLAB 中有哪些命令,让人相见恨晚?