codeforces 848B :计数

题意:给出一个坐标平面[ 0..w , 0..h ]在坐标平面的x轴和y轴,分布有一些点,其中x轴上的点运动方向是向上,初始横坐标范围是[1 .. w],y轴同理。同时每个点会有一个延迟t,前t单位的时间这个点是固定不动的,在t时刻启动,每个点的速率都是 1/单位时间。比如说某个点(p,0)延迟为t,[0 .. t ]时刻他的坐标都是(p,0),在K(>t)时刻坐标是(p,K-t)。当两个点在某时刻运动到同一点的时候发生碰撞,碰撞结果是两球交换运动方向。不存在相同的两点(出发位置相同且延迟也相同)。每个点运动到坐标平面边界停止。一个点的一生可以连续发生多次碰撞。求每个点的停止位置。


题解:碰撞只会发生在一个正在向上走的点和一个正在向右走的点之间,设他们分别是从(x,0)延迟tx出发、从(0,y)延迟ty出发。那么碰撞发生的条件是y+tx=x+ty也就是tx-x=ty-y。碰撞发生的结果是两个球呼唤方向,速率不变,可以看作每个球都是按照自己的方向走直线,当相遇的时候,会把身上的标签交换。继续考虑连续相遇问题:假设1球和2球相遇,满足 t1-p1=t2-p2。现在第一号点走的是2球的路径,那么2球可能继续和3球相遇,满足t2-p2=t3-p3……于是问题很明显了,吧t-p相同的点拿出来,这些点之间互相可能产生碰撞,而其他的球怎么走与我无关。然后考虑碰撞的时间顺序:某一个横线,有一系列和他碰撞的竖线,那么碰撞顺序必然是按照横坐标升序。竖线同理。于是我们把t-p相同的所有线拿出来,画成一个网格,那么一个点一生的运动路径可以这样描述:先沿着自己的路径走,然后进入网格。在网各种,遇到一个交点就变一次方向(上变右,右变上)。最后离开网格,走某个点的最后一段,然后结束生命。这就变成了一个xjb根据横线竖线的个数决定某个点他会走到上边还是右边以及具体是上边的那里和右边的那里。这种xjb的题目我都叫做计数问题233


每个点可能会被其他点拐卖,也可能不会,被拐卖的时候,每个点可以O(1)出答案,没被拐卖的时候不用管他。 双指针模拟,复杂度是O(n)的。常数懒得算了

Code:

#include<bits/stdc++.h>
using namespace std;
const int MAX = 1e5+100;
tuple <int,int,int> x[MAX],y[MAX];
tuple<int,int> ans[MAX];
int id[MAX];
int n,w,h,xtot,ytot;
void input(){
	scanf("%d%d%d",&n,&w,&h);
	for (int i=1;i<=n;i++){
		int g,p,t;
		scanf("%d%d%d",&g,&p,&t);
		if (g==1){
			ans[i] = make_tuple(p,h);
			x[xtot++] = make_tuple(t-p,p,i); 
		}else{
			ans[i] = make_tuple(w,p);
			y[ytot++] = make_tuple(t-p,p,i);
		}
		id[i]=i;
	}
}
bool cmp(tuple<int,int,int> a,tuple<int,int,int> b){
	if (get<0>(a)==get<0>(b)){
		return get<1>(a)<get<1>(b);
	}
	return get<0>(a)<get<0>(b);
}
void solve(){
	sort(x,x+xtot,cmp);
	sort(y,y+ytot,cmp);
	/*
	for (int i=0;i<xtot;i++){
		printf("x[%d]=(%d,%d,%d)\n",i,get<0>(x[i]),get<1>(x[i]),get<2>(x[i]));
	}
	for (int i=0;i<ytot;i++){
		printf("y[%d]=(%d,%d,%d)\n",i,get<0>(y[i]),get<1>(y[i]),get<2>(y[i]));
	}
	*/
	for (int i=0,j=0,ii,jj;i<xtot;i=ii){
		int xx = get<0>(x[i]);
		ii=i;
		while(ii<xtot&&get<0>(x[ii])==xx){
			ii++;
		} 
		while (j<ytot&&get<0>(y[j])<xx){
			j++;
		}
		jj =j;
		while (jj<ytot&&get<0>(y[jj])==xx){
			jj++;
		}
		int sx = ii-i;
		int sy = jj-j;
		if (sy==0){
			continue;
		}
//		cout<<xx<<" "<<i<<","<<ii<<" "<<j<<","<<jj<<endl;
		for (int k=i;k<ii;k++){
			if (sy>=ii-k){
				id[get<2>(x[k])] = get<2>(y[j+ii-1-k]);
			}else{
				id[get<2>(x[k])] = get<2>(x[k+sy]);
			}
		}
		for (int k=j;k<jj;k++){
			if (sx>=jj-k){
				id[get<2>(y[k])] = get<2>(x[i+jj-k-1]);
			}else{
				id[get<2>(y[k])] = get<2>(y[k+sx]);
			}
		}
	}
	for (int i=1;i<=n;i++){
		printf("%d %d\n",get<0>(ans[id[i]]),get<1>(ans[id[i]]));
	}
}
int main(){
	input();
	solve();
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值