【loli的胡策】NOIP训练7.15(签到+dp+线段树)

T2题目:

【问题描述】

    平面内给  n 个点,记横坐标最小的点为A,最大的点为B,现在小Y想要知道在

每个点经过一次A 点两次)的情况下从A走到B,再回到A 的最短路径。但他是个强

迫症患者,他有许多奇奇怪怪的要求与限制条件:

    1.A走到B 时,只能由横坐标小的点走到大的点。

    2.B 回到A 时,只能由横坐标大的点走到小的点。

    3.有两个特殊点b1 b2b1 0 n-1 的路上,b2 n-1 0 的路上。

    请你帮他解决这个问题助他治疗吧!

 【输入格式】

    第一行三个整数n,b1,b2,(0<b1b2<n-1 b1<>b2)n 表示点数,从0 n-1

号,b1 b2 为两个特殊点的编号。

    以下n 行,每行两个整数 xy 表示该点的坐标(0<=xy<=2000),从0 号点顺序

给出。DoctorGao为了方便他的治疗,已经将给出的点按x 增序排好了。

 【输出格式】

    仅一行,即最短路径长度(精确到小数点后面2 位)

 【样例输入输出】

paths.in    

5 1 3

1 3

3 4

4 1

7 5

8 3

paths.out

18.18

【样例解释】 最短路径:0->1->4->3->2->0

 【数据范围】

    20%的数据n<=20

    60%的数据n<=300

    100%的数据n<=1000

    对于所有数据x,y,b1,b2如题目描述.

题解:

这是个dp,考场上怎么也没往这方面想。。。我们可以考虑为从1走到n两遍,求两条不相交的路径最小

为了不转移到f[i][i]就让k=max(i,j)+1

然后特殊点什么的特判就好了

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
struct hh
{
	int x,y;
}s[1005];
int n,b1,b2;
bool a[1005];
double f[1005][1005],minn=1000000000;
double ll(int a,int b)
{
	return sqrt(double((s[a].x-s[b].x)*(s[a].x-s[b].x)+(s[a].y-s[b].y)*(s[a].y-s[b].y)));
}
int main()
{
	freopen("paths.in","r",stdin);
	freopen("paths.out","w",stdout);
	int i,x,y,j;
	scanf("%d%d%d",&n,&b1,&b2);b1++,b2++;
	for (i=1;i<=n;i++)
	  scanf("%d%d",&s[i].x,&s[i].y);//f[i][j]表示第一个点走到i,第二个点走到j的最短距离 
	memset(f,0x7f,sizeof(f));
	f[1][1]=0;
	for (i=1;i<=n;i++)
	  for (j=1;j<=n;j++)
	    if (i!=j || i==1) 
		{
			int k=max(i,j)+1;
			if (k==n+1)
			{
				if (j==n) f[n][n]=min(f[n][n],f[i][j]+ll(i,n));
				if (i==n) f[n][n]=min(f[n][n],f[i][j]+ll(j,n));
				continue;
			}
			if (k!=b1) f[i][k]=min(f[i][k],f[i][j]+ll(j,k));
			if (k!=b2) f[k][j]=min(f[k][j],f[i][j]+ll(i,k));		
		}
	printf("%.2lf",f[n][n]);  
}


T3题目:

时间限制 : 20000MS空间限制 : 265536 KB

问题描述 

刚拿到驾照的 KJ 总喜欢开着车到处兜风,玩完了再把车停到阿 Q的停车场里,虽然她对自己停车的水平很有信心,但她还是不放心其他人的停车水平,尤其是 Kelukin。于是,她每次都把自己的爱车停在距离其它车最远的一个车位。KJ觉得自己这样的策略非常科学,于是她开始想:在一个停车场中有一排车位,从左到右编号为 1 n,初始时全部是空的。有若干汽车,进出停车场共 m次。对于每辆进入停车场的汽车,会选择与其它车距离最小值最大的一个车位,若有多个符合条件,选择最左边一个。KJ想着想着就睡着了,在她一旁的 Kelukin想帮她完成这个心愿,但是他又非常的懒,不愿意自己动手,于是就把这个问题就留给了你:在 KJ 理想的阿 Q 的停车场中,给你车辆进出的操作序列,依次输出每辆车的车位编号。

输入格式 
第一行,两个整数 n m,表示停车场大小和操作数; 
接下来 m 行,每行两个整数 F x F 1表示编号为 x 的车进停车场; F 2表示编号为 x 的车出停车场; 
保证操作合法,即:出停车场的车一定目前仍在停车场里;停车场内的车不会超过 n

输出格式 
对于所有操作 1,输出一个整数,表示该车车位的编号

样例输入 
7 11 

1 15 
1 123123 
1 3 
1 5 
2 123123 
2 15 
1 21 
2 3 
1 6 
1 7 
1 8

样例输出 







3

提示 
【数据范围】 
30%的数据n<=1000m<=1000 
60%
的数据 n<=200000m<=2000 
100%的数据 nm<=200000 
车的编号小于等于 10^6

题解:

这个题目是个线段树,其实都是基本的操作,考场上脑子一抽就写挂了。。。。

线段树中维护四个量(全都是所在区间内):l(最左边种树的坐标),r(最右边种树的坐标),mid(最近邻两棵树的最大距离),p(最近邻两棵树的中间位置)

这样每个操作一都可以看成是整个区间的询问:最左边到1是不是最大的距离(答案是1),mid是不是最大距离(答案是p),最右边到r是不是最大距离(答案是n)

这样updata也好维护:now的左就是最左,now的右就是最右,now的mid就是左边的mid,跨边界的mid,和右边的mid比较

操作二是一个添加or删除的过程,删除就是全为0,添加就把所在位置添加进去

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#define INF 1e9
#define N 200000
using namespace std;
int wz[1000006];
struct hh
{
	int l,r,mid,en;
}tree[N*4];
void updata(int now)
{
	if (tree[now<<1].l!=0) tree[now].l=tree[now<<1].l; else tree[now].l=tree[(now<<1)|1].l;
	if (tree[now<<1|1].r!=0) tree[now].r=tree[now<<1|1].r; else tree[now].r=tree[now<<1].r;
	tree[now].mid=tree[now<<1].mid;
	tree[now].en=tree[now<<1].en;
	if (tree[now<<1|1].l!=0 && tree[now<<1].r!=0) 
	{
		int ss=(tree[now<<1|1].l-tree[now<<1].r)/2;
		if (ss>tree[now].mid)
		{
			tree[now].mid=ss;
			tree[now].en=(tree[now<<1|1].l+tree[now<<1].r)/2;
		}	
	}	
	if (tree[now<<1|1].mid>tree[now].mid)
	{
		tree[now].mid=tree[now<<1|1].mid;
		tree[now].en=tree[now<<1|1].en;
	}
}
void work(int now,int l,int r,int mb,int id)
{
	if (l==r)
	{
		if (id==1)
	{
		tree[now].l=l; tree[now].r=r;
		tree[now].mid=0; tree[now].en=0;
	}
	else
	{
		tree[now].l=0; tree[now].r=0;
		tree[now].mid=0; tree[now].en=0;
	}
	return;
	}
	int mid=(l+r)>>1;
	if (mb<=mid) work(now<<1,l,mid,mb,id);
	else work(now<<1|1,mid+1,r,mb,id); 
	updata(now);
}
int main()
{
	freopen("park.in","r",stdin);
	freopen("park.out","w",stdout);
	int n,m,i,j;
	scanf("%d%d",&n,&m);
	for (i=1;i<=m;i++)
	{
	  	int id,x;
	  	scanf("%d%d",&id,&x);
	  	if (id==1)
	  	{
	  		if (tree[1].l==0)
	  		{
	  		  	wz[x]=1;
			}
			else
			{
				int sum=-INF;
	  		if (tree[1].l-1>sum)
	  		{
	  			sum=tree[1].l-1; wz[x]=1; 
			}
			if (tree[1].mid>sum)
	  		{
	  			sum=tree[1].mid; wz[x]=tree[1].en;
		    }	
			if (n-tree[1].r>sum)
	  		{
	  			sum=n-tree[1].r; wz[x]=n;
			}
			}
	  		printf("%d\n",wz[x]);
	  		work(1,1,n,wz[x],1);
		}
		else work(1,1,n,wz[x],2);
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值