例题:http://poj.org/problem?id=2018 poj2018
一道数形结合从而复杂度的题目。 很简单 但看了我一个下午。
朴素的暴力模拟O(n^2)的复杂度铁定超时。 所以我们需要进行优化。
浅谈数形结合思想在信息学竞赛中的应用
找一个长度不小于L的子串让里面1的比例最大,如果有多个,找长度最短的,如果还有多个,找最靠前的。
我也是想到用sum存前面的数字之和,找最大的(sum[b]-sum[a])/(b-a)。可是没想出来怎么找a,遍历一遍肯定是不行的会超时。。还是在网上搜了做法,以前没见过这样的。
这相当于一个非递减数列,i是x坐标,sum[i]是y坐标,在这个图上找y-x>=L的斜率最大的两个点。用一个队列保存当前可能用到的起点,设i从L递增到N,我们每次在队列中加入i-L作为起点,设rear为当前队列中最后一个元素。如果发现rear和i-L这两个点的斜率小于等于rear-1和rear这两个点的斜率,也就是rear-1,rear,i-L三个点一次连成的曲线不是下凸的,就可以把rear这个点删掉了。这是因为后面再出现的点怎么都不可能和rear这个点构成最大斜率(可以自己画个图看一下)。从后删到不能删后,把i-L加到最后。不光可以从后删掉一些没用的点,还可以从前删掉一些。如果front那个点和i点的斜率小于front+1和i的斜率,那么front这个点可以删掉,因为y是单调不减的,后面再出现的点如果可能更新最大斜率的话,明显和front+1构成的斜率更优。这样又从前删掉了一些点。删完后i和当前的front就是i点作为end的最优答案。
总之,队列中的点都是当前有可能作为起点的,是一条下凸曲线。这样就比遍历一遍省时间,省去了一些没用的点。
附上大神的代码(我加了点备注)
#include<iostream>
#include<cstdio>
using namespace std;
int s[100010],q[100010]; //q数组中存的是X坐标,坐标为(q[x],s[q[x]])
int n,m,t,h;
double ans=-100000;
double max(double x,double y)
{
return x>y?x:y;
}
double K(int x,int y)
{
return double(s[y]-s[x])/double(y-x); //这样还是有点风险的,可能斜率不存在
}
bool maintain(int k,int i,int j)
{
return K(i,k)>K(k,j);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int x; scanf("%d",&x);
s[i]=s[i-1]+x;
}
for(int i=0;i<=n-m;i++) //从第0个点开始遍历
{
while (t>h && maintain(q[t],q[t-1],i)) t--; //从后面进行删点操作,检测i t-1 t 这三个的是否合法
q[++t]=i; int j; //加点
for(j=t;j>h && maintain(q[j],q[j-1],i+m);j--); //从后面进行删点操作,检测j j-1 i+m 这三个的是否合法
h=j; //记录当前数组最后一个点;
ans=max(ans,K(q[j],i+m));
}
printf("%d\n",int(ans*1000));
return 0;
}
本文介绍了一种利用数形结合思想优化复杂度的算法解决特定问题的方法。通过将问题转化为寻找非递减数列中满足条件的最大斜率,使用队列来存储潜在的起点,并通过前后删除无效点的方式减少计算量。

293

被折叠的 条评论
为什么被折叠?



