uoj 245. 【UER #7】天路 (近似算法)

245. 【UER #7】天路
隆冬将至,几天后跳蚤国便会迎来寒冬,这对于以血肉之躯和飞机搏斗的跳蚤们来说并不是件好事……然而在悠悠历史岁月中,跳蚤国早已有了应对严寒的应急措施方案!
在跳蚤国王的带领下,跳蚤们准备启动天路热能塔 —— 红米 note7(红米 note7 为发烧而生)。这座热能塔高耸入云,直接穿出大气层从太空中直接吸收太阳光,垂直向下将热能送往跳蚤国各个角落。热能塔的制造工艺巧夺天工,被誉为“带来温暖的天路”。
但是跳晚们为了让跳蚤们都因为天气寒冷赖在被子里不肯起床,在热能塔启动后一定会歇斯底里地进攻。跳蚤国高级间谍的情报显示,跳晚国计划将发射 n台三星 note7 向热能塔发起进攻。进攻将会按一定顺序进行,其中第 ii 次进攻的高度为 ai(1≤i≤n)。
为了防止热能塔被炸毁,跳蚤国王特地派尛焱轟(一种新型交通工具,运载能力是小火车的三次幂)运送来了跳蚤们刚研制出不久的新型材料 —— Nokia1050。跳蚤们将会把 Nokia1050 装在热能塔上的某一连续的高度区间上以抵挡进攻。
现在,跳蚤国王想在热能塔受损程度和材料消耗量之间进行取舍。所以对于每个 2≤k≤n,跳蚤国王想知道整个攻击过程中如果想让 Nokia1050 在某一时段至少挡住连续 k次攻击,那么安装 Nokia1050 的高度区间的长度至少是多少。其中,若高度区间为 [l,r],则长度为 r−l。
事实上,间谍的消息也不见得会多么靠谱,所以跳蚤国王仅想知道一个不那么准确的答案。具体来说:
如果对于每个 k你输出的答案 ck与标准答案 c^k 的相对误差均不超过 5%,则算作正确。即:
∣ck−c^k∣≤5%⋅c^k
输入格式
第一行一个正整数 n,保证 n≥2。
第二行 n个正整数 a1,…,an,按顺序给出每次进攻时三星 note7 的高度。
输出格式
输出 n−1行,其中第 k−1行表示至少抵挡连续 k次攻击时所需的最短高度区间长度。(2≤k≤n)
因为十分重要所以说两遍,如果对于每个 kk 你输出的答案 ck与标准答案 c^k的相对误差均不超过 5%,则算作正确。即:∣ck−c^k∣≤5%⋅c^k
样例一
input
4
1 7 5 2
output
2
5
6
explanation
当 k=2时,最优高度区间为 [5,7];
当 k=3时,最优高度区间为 [2,7];
当 k=4 时,最优高度区间为 [1,7];
注意 k=2时不能选择高度区间 [1,2],虽然能够拦截下第 1次和第 4次攻击,但这两次攻击并不连续。
样例二
input
10
26 723 970 13 422 968 875 329 234 983
output
93
546
639
734
749
957
957
957
970
explanation
样例输出给出的为准确答案,注意下面的输出也是可接受的:
93
540
630
730
740
960
960
960
970
样例三
见样例数据下载。
限制与约定

对于所有数据,保证 1≤ai≤106。
时间限制:1s

空间限制:256MB


题解:
算法一:O(n^2logn)
暴力枚举区间,然后用线段树维护区间最值,O(logn)查询。也可以用rmq,O(1)查询。
算法二:
对于 6,7 号测试点,n<=10^5。a[i]数据随机
随机数据的前缀最小值只会变化 O(logn)次
所以当我们固定左端点移动右端点时,区间内最大值与最小值至多只会变化 O(logn) 次。
拿个单调栈维护一下……每次给对应的长度区间更新答案。
算法三:
还是数据随机的情况,观察数据我们注意到如果区间长度够长,那么答案就非常接近10^6。
所以我们设一个几百左右的值 l,然后处理小于 l 时的答案,剩下来的输出 10^6 即可。
同样可以获得 70 分。
算法四:
你想,要是直接把权值除以一个大常数,做完之后再乘回来,看上去好像很近似!但是冷静冷静就会发现这样做出来是个绝对误差的,如果碰到233333−233332=1,除去一个大常数很显然是0,那么你再乘回去也没用。
一个思路是,我们应该是枚举一个 k ,然后某种方式维护一下,但这个看上去就很没办法改造为近似算法。
那如果告诉你一个常数 c,我们来把答案 ck 划一划范围,那 ck≥c 可做吗?
因为最终的答案肯定是单调递增的,所以范围肯定是一段连续的 k。我们可以两个指针扫一下,对于每个ck我们求解出最长的满足当前答案的长度x,并用ck更新长度ans[x]的答案。不是每个长度都有可能被计算到,所以最后我们需要ans[x]=max(ans[x],ans[x+1])更长的都能满足,那么当前的必然能满足。(如何求满足条件的最长长度,设两个指针i,j,如果getmx(i,j)-getmn(i,j)在要求的范围之内,就不断的后移j.然后更新答案,这时候j可能不能再移了,所以左指针i右移,但是这样会跳过很多的区间,其实没关系,因为我们要求的是最长的长度,所以如果j不动,i后移那么答案一定不会大于上次的答案。所以我们可以O(n)求解)
于是我们就可以每次找找 c,划分 k 的范围。当范围足够小的时候,就可以把这一段的 kk 都用同一个数近似了。
事实上,你只需要取:
1.05^0,1.05^1,1.05^2,1.05^3,…
每一段里面随便找个数当答案一定不会超过相对误差的容许范围。
O(nlog1+ϵV)=O(nlogV/log(1+ϵ))=O(ϵ−1nlogV).
这个算法其实说明,所有 f(i)随 i递增且有个算法告诉我 f(i)≥k的第一个 i在哪里的话,都可以套这个近似算法!

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<ctime>
#define N 200003
using namespace std;
int n,m;
int tr[N*4],tr1[N*4],a[N];
int l[N],f[30][N],f1[30][N];
double ans[N],x;
struct data
{
	int maxn,minn;
};
void rmq()
{
	for (int i=1;i<=n;i++) f[0][i]=a[i];
	for (int i=1;i<=19;i++)
	 for (int j=1;j<=n;j++)
	  if (j+(1<<i)-1<=n)
	   f[i][j]=max(f[i-1][j],f[i-1][j+(1<<(i-1))]);
	for (int i=1;i<=n;i++) f1[0][i]=a[i];
	for (int i=1;i<=19;i++)
	 for (int j=1;j<=n;j++)
	  if (j+(1<<i)-1<=n)
	   f1[i][j]=min(f1[i-1][j],f1[i-1][j+(1<<(i-1))]);
	int j=0;  
    for (int i=1;i<=n;i++)  
     {  
        if ((1<<(j+1))<=i)  j++;  
        l[i]=j;  
     }  
}
int getmx(int x,int y)
{
	int t=l[y-x];
	return max(f[t][x],f[t][y-(1<<t)+1]);
}
int getmn(int x,int y)
{
	int t=l[y-x];
	return min(f1[t][x],f1[t][y-(1<<t)+1]);
}
int getl(int  x)
{
	int i,j,ans;
	i=j=1; ans=0;
	while (i<=n)
	{
		while (j<=n&&getmx(i,j)-getmn(i,j)<=x)  j++;
		ans=max(ans,j-i);
		i++;
	}
	return ans;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    rmq(); 
    memset(ans,127,sizeof(ans));
    int i=getl(0);
    ans[i]=min(ans[i],x);
    for (x=1.05;x<=1050000;x*=1.05)
    {
    	int i=getl((int)x);
    	ans[i]=min(ans[i],x);
	}
	for (int i=n-1;i>=1;i--)
	 ans[i]=min(ans[i],ans[i+1]);
	for (int i=2;i<=n;i++)
	 printf("%d\n",(int)ans[i]);
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值