CH0601 Genius ACM

这个题弄得我怀疑人生,读题就走了半天弯路

一开始半天都没搞清楚,原来是 让我连续地分段,不必打乱重排,故想办法找到分段的端点值即可
在每次找到一个端点值之后,与下次的衔接稍微麻烦

剩下的就是愉快的倍增了

算法回顾:

题目给出固定的数列a,要求将数列a分段,要求每一段的“校验值”要<=k。

“校验值”求法:从你分的段中取出m对数,求每对数差的平方之和的最大值 (设Di为每对数的差 ,“校验值”:SPD=∑Di^2最大)

求得“校验值”的方法可用贪心,将a1~an 排序,让最大和最小,次大和次小这样地取出来,然后直接就是最大值

这个贪心的正确性可以证明,我们用反证的方法

设a为排好序的数列,设S1用取头尾的方法,S2用取最大和次小,最小和次大的方法

 

故用取头尾的方法更优

为了尽量分组最少,我们还需要用优化的方法找分组断点,这里可以想到二分、倍增。

一开始想到二分,然而显然不是最优,而且还改了半天

40分二分代码:

 

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<cmath>
 5 #include<algorithm>
 6 #define llint long long
 7 #define pau system("pause")
 8 using namespace std;
 9 int t;
10 int n,m,k;
11 int a[500005];
12 int tmp[52000];
13 inline bool check(int l,int r)
14 {
15     llint sum=0;
16     int f=0;
17     for(int i=l;i<=r;i++) tmp[++f]=a[i];//将原来的复制过来排序一次
18     stable_sort(tmp+1,tmp+1+f);
19     for(int i=1;i<=min(m,(r-l+1)/2);i++)
20     {
21         int s1=tmp[i]-tmp[f-i+1];
22         sum+=s1*s1;
23     }
24     if(l==r) sum=0;
25     if(sum<=k) return true;
26     else return false;
27 }
28 int main()
29 {
30     scanf("%d",&t);
31     while(t--)
32     {
33         scanf("%d%d%d",&n,&m,&k);
34         for(int i=1;i<=n;i++)
35         {
36             scanf("%d",&a[i]);
37         }
38         //二分出一个端点值使得左半边spd合法且尽量长
39         int L=1;//未解决区间的左端点值
40         int cnt=0,rpoint=1;
41         while(L<=n)//注意这里进入下一段未解决区间,L值要到这个r的右边一个
42         {
43             int l=L,r=n;
44             rpoint=L;//防止找不到r
45             while(l<=r)//用二分法求下一次的右端点
46             {
47                 int mid = (l+r)>>1;
48                 if(check(L,mid)) rpoint = mid, l = mid+1;//扩大合法答案
49                 else r = mid-1;
50             }
51             L = rpoint+1;
52             cnt++;
53         }
54         cout<<cnt<<endl;
55     }
56 }
View Code

 

 

 然而这道题的特征显然不是二分

我们可以设计一种更有目的性的解法,每次向右找一个尽量远的端点值:倍增

而且,光有倍增是不行的,由于重复排序我们每次又浪费大量的时间,所以我们想到了归并排序

为了卡常数,读优写优都上了

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<cmath>
 5 #include<algorithm>
 6 #define llint long long
 7 #define re register
 8 #define pau system("pause")
 9 using namespace std;
10 template <typename ty> inline void read(ty &x)
11 {
12     x=0;int f=1;re char c=getchar();
13     for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=-1;
14     for(;c>='0'&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
15     x*=f;
16 }
17 template <typename ty> inline void write(ty x)
18 {
19     if(x<0) putchar('-'),x=-x;
20     if(x>9) write(x/10);
21     putchar(x%10+48);
22 }
23 int t;
24 int n,m;
25 llint k;
26 llint a[500005];
27 llint s[500005],s2[500005];//为了用归并思想提速 s为常有序数列, s2用来试探答案
28 inline void resort(int l,int rp,int r)
29 {
30     for(re int i=rp+1;i<=r;i++) s[i]=a[i];//将原来的复制过来排序一次
31     stable_sort(s+rp+1,s+r+1);
32     merge(s+l,s+rp+1,s+rp+1,s+r+1,s2+l);
33 }
34 inline bool check(int l,int rp,int r)//rp为原来的r,r为增加了len的r
35 {
36     llint sum=0;
37     resort(l,rp,r);//归并提速
38     for(re int i=1;i<=min(m,(r-l+1)/2);i++)//注意这里一定要加min(m,(r-l+1)/2),因为可能m个不够取
39     {
40         llint s1 = s2[l+i-1]-s2[r-i+1];
41         sum += s1*s1;
42         if(sum>k) return false;//魔鬼般的剪枝,可以多对一个点
43     }
44     
45     if(l==r) sum=0;
46     if(sum<=k)
47     {
48         for(re int i=l;i<=r;i++) s[i]=s2[i];//此处的r已被认可,下次传来的r一定在右边,故排好序的数列可以保存
49         return true;
50     }
51     else return false;
52 }
53 
54 int main()
55 {
56 //    freopen("input2","r",stdin);
57     read(t);
58     while(t--)
59     {
60         read(n),read(m),read(k);
61         for(re int i=1;i<=n;i++)
62         {
63             read(a[i]);
64         }
65         int l=1;
66         int cnt=0;
67         
68         s[1]=a[1];//恶魔般的初始化
69         
70         while(l<=n)
71         {
72             int r=l;//欲寻找的右端点值
73             int len=1;
74             while(len!=0)//通过倍增的方法找右端点
75             {
76                 if(r+len<=n && check(l,r,r+len)) r+=len,len<<=1;
77                 else len>>=1;
78             }
79             l=r+1;//下一次的左端点的衔接
80             cnt++;
81         }
82         write(cnt),putchar('\n');
83     }
84 }
View Code

 

全部评论

相关推荐

不愿透露姓名的神秘牛友
06-27 14:11
很喜欢小米的新车,校招薪资每月22k,攒多久能买?
测试糕手手:别看工资,先看现金流存款。有50W存款以上再考虑,车是消耗品,选适合自己的重要。你有钱就当我没说过
点赞 评论 收藏
分享
本神尊:看来是没招到小红薯上的人
点赞 评论 收藏
分享
06-13 17:33
门头沟学院 Java
顺序不记了,大致顺序是这样的,有的相同知识点写分开了1.基本数据类型2.基本数据类型和包装类型的区别3.==和equals区别4.ArrayList与LinkedList区别5.hashmap底层原理,put操作时会发生什么6.说出几种树型数据结构7.B树和B+树区别8.jvm加载类机制9.线程池核心参数10.创建线程池的几种方式11.callable与runnable区别12.线程池怎么回收线程13.redis三剑客14.布隆过滤器原理,不要背八股,说说真正使用时遇到了问题没有(我说没有,不知道该怎么回答了)15.堆的内存结构16.自己在写项目时有没有遇见过oom,如何处理,不要背八股,根据真实经验,我说不会17.redis死锁怎么办,watchdog机制如何发现是否锁过期18.如何避免redis红锁19.一个表性别与年龄如何加索引20.自己的项目的QPS怎么测的,有没有真正遇到大数量表21.说一说泛型22.springboot自动装配原理23.springmvc与springboot区别24.aop使用过嘛?动态代理与静态代理区别25.spring循环依赖怎么解决26.你说用过es,es如何分片,怎么存的数据,1000万条数据怎么写入库中27.你说用limit,那么在数据量大之后,如何优化28.rabbitmq如何批次发送,批量读取,答了延迟队列和线程池,都不对29.计网知不知道smtp协议,不知道写了对不对,完全听懵了30.springcloud知道嘛?只是了解反问1.做什么的?短信服务,信息量能到千万级2.对我的建议,基础不错,但是不要只背八股,多去实际开发中理解。面试官人不错,虽然没露脸,但是中间会引导我回答问题,不会的也只是说对我要求没那么高。面完问我在济宁生活有没有困难,最快什么时候到,让人事给我聊薪资了。下午人事打电话,问我27届的会不会跑路,还在想办法如何使我不跑路,不想扣我薪资等。之后我再联系吧,还挺想去的😭,我真不跑路哥😢附一张河科大幽默大专图,科大就是大专罢了
查看30道真题和解析
点赞 评论 收藏
分享
Gaynes:查看图片
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务