码迷,mamicode.com
首页 > 其他好文 > 详细

CH0601 Genius ACM

时间:2018-12-30 17:20:33      阅读:240      评论:0      收藏:0      [点我收藏+]

标签:define   lse   nbsp   using   names   acm   stdin   cstring   答案   

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

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

剩下的就是愉快的倍增了

算法回顾:

题目给出固定的数列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

 

CH0601 Genius ACM

标签:define   lse   nbsp   using   names   acm   stdin   cstring   答案   

原文地址:https://www.cnblogs.com/tythen/p/10199676.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!