NOIP2012 开车旅行 倍增与模拟结合后的调试神题

#Problem
偷个懒一定不会被发现的
#题外话
话说上午才考完令人发指的**[真·NOIp膜你赛]**,然后整个人都不好了,而且这道题目还是从昨天晚上调一直调到今天下午才A的,然而dalao们都是1A。尤其是中间在调试的时候,内心已经爆炸了。

注 : N O I p 指 N O I p l u s / N O I p r o f e s s i o n a l 注:NOIp指NOIplus/NOIprofessional NOIpNOIplus/NOIprofessional
#Solution
其实我觉得当年NOIP出这道题还是比较良心的,暴力分有70分,也就是说单纯去模拟就有70分诶,所以打暴力的性价比是很高的!尤其在考试的时候,我肯定不会优先打这道题的正解,除非你有像 @kb 一样深厚的功力。

很明显,如果打单纯的模拟是拿不了满分的,但是看看数据范围,我们会发现只需要一个log级别的优化就可以A了,那么很容易想到用倍增进行优化,令两天为一轮,那么按照轮次来进行倍增,于是就可以预处理出走 2 i 2^i 2i轮之后走到的节点以及小A走的公里数,小B走的公里数。然后按照题意分别枚举即可。但是这道题目还有一个坑点,就是枚举完轮数之后,小A有可能还是可以走,那么在最后的时候还要特判一下。

#Code

#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=100010;
struct data{
	int l,r,h,id;
	bool operator < (const data &x)const {return h<x.h;}
}c[maxn];
ll x;
int n,m,da,db,l,r,t,ans;
double minx=0x3f3f3f3f;
int p[maxn],na[maxn],nb[maxn],a[maxn],b[maxn],f[maxn][21],disa[maxn][21],disb[maxn][21];
int abs(int x){return x<0?-x:x;}
void init(int now)
{
	int l=c[p[now]].l,r=c[p[now]].r;
	if(!r||(l&&c[p[now]].h-c[l].h<=c[r].h-c[p[now]].h))//the left point is near
	{
		nb[now]=c[l].id;
		int ll=c[l].l;
		if(!r||(ll&&c[p[now]].h-c[ll].h<=c[r].h-c[p[now]].h))
		  na[now]=c[ll].id;
		else
		  na[now]=c[r].id;
	}
	else
	{
		nb[now]=c[r].id;
		int rr=c[r].r;
		if(!rr||(l&&c[p[now]].h-c[l].h<=c[rr].h-c[p[now]].h))
		  na[now]=c[l].id;
		else
		  na[now]=c[rr].id;
	}
	if(l)
	  c[l].r=r;
	if(r)
	  c[r].l=l;
}
void prepare()
{
	for(int j=1;j<=20;j++)//请注意循环次序!
	  for(int i=1;i<=n;i++)
	  {
	  	f[i][j]=f[f[i][j-1]][j-1];
	  	disa[i][j]=disa[i][j-1]+disa[f[i][j-1]][j-1];
	  	disb[i][j]=disb[i][j-1]+disb[f[i][j-1]][j-1];
	  }
}
void get(ll x,int p)
{
	da=db=0;
	for(int i=20;i>=0;i--)
	  if(f[p][i]&&(ll)(da+db+disa[p][i]+disb[p][i])<=x)//为了以防万一
	  {
	  	da+=disa[p][i];
	  	db+=disb[p][i];
	  	p=f[p][i];
	  }
	if(na[p]&&da+db+disa[p][0]<=x)
	  da+=disa[p][0];//特判a单独走的情况
}
int main()
{
	freopen("in.txt","r",stdin);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&c[i].h);
		c[i].id=i;
	}
	sort(c+1,c+n+1);
	for(int i=1;i<=n;i++)
	{
		p[c[i].id]=i;//可以说类似于一个离散化吧
		c[i].l=i-1;c[i].r=i+1;
	}
	c[1].l=c[n].r=0;
	for(int i=1;i<=n;i++)
	  init(i);
	for(int i=1;i<=n;i++)
	{
		f[i][0]=nb[na[i]];
		disa[i][0]=abs(c[p[i]].h-c[p[na[i]]].h);
		disb[i][0]=abs(c[p[f[i][0]]].h-c[p[na[i]]].h);
	}
	prepare();
	scanf("%lld%d",&x,&m);
	for(int i=1;i<=n;i++)
	{
		get(x,i);
		if(db&&da*1.0/db<minx)
		{
			minx=da*1.0/db;
			ans=i;
		}
	}
	printf("%d\n",ans);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%lld",&t,&x);
		get(x,t);
		printf("%d %d",da,db);
		if(i!=m)//可能是因为我脸黑,被洛谷判了too many lines才加的
		  printf("\n");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值