HDU 6688 Rikka with Traffic Light(DP+斜率优化)

47 篇文章 0 订阅

题目
题意: N个人过马路,分别纵向过和横向过,红灯时只可以纵向过,绿灯时反之,纵向过需要 T 1 s T_1s T1s,横向过需要 T 2 s T_2s T2s,每个人在 t i t_i ti到达马路边,求最小的等待时间和,等待时间为他开始过马路的时间减去到马路边的时间。
N &lt; = 3 e 3 N&lt;=3e3 N<=3e3

题解:

官方题解:
首先可以发现一定存在一个最优方案,所有绿灯区间长度都大于等于 T 1 T_1 T1 ,所有红灯区间长度都大于等
T 2 T_2 T2 。如果如果小于的话,反正这一段时间也没有办法让人过红绿灯,不如就不变了。接着考虑如果
灯在第 t t t时刻变成了绿灯,最优解中它会在什么时候变成红灯。(对于红灯变成绿灯的分析类似)
第一种可能性是在 t + T 1 t+T_1 t+T1时刻变成红灯,在这种情况下,一定有在 之前(包括 ) 时刻到达且还没有
过马路的人,不然这一段没有人能过马路。
第二种可能性是在 t + T 1 t+T_1 t+T1时刻之后,考虑在这段绿灯中最后过马路的那个人 ,他会在 时刻后
通过马路。因此这段绿灯在第 t + T 1 t+T_1 t+T1时刻结束一定不亏。
我们把所有 t + T 1 t+T_1 t+T1或者 t + T 2 t+T_2 t+T2 (取决于 i i i的种类) 作为关键点,设 f i f_i fi为在第 i i i个人对应的关键切换成
另一种灯时的已经能确定过马路时间的人的总等待时间(不妨假设第 i i i个人是第一类人)。注意这儿已
经能确定过马路时间的人包括在 t i t_i ti时刻之前到达的第一类人和第 t i + T 1 t_i+T_1 ti+T1时刻之前达到的第二类人 (最
开始发现的性质保证了他们都能在 t i + T 1 t_i+T_1 ti+T1) 时刻过马路。
考虑转移,首先从 t i + T 1 t_i+T_1 ti+T1开始,最优方案可能会进行若干段第一种可能性的转移,接着通过一个第二
种可能性的转移直接跳到后面的某一个关键点。因为第一种可能性要求必须要有对应的人在等待,因此
第一种转移最多进行 次。我们可以枚举第一段转移进行的次数,并求出对应的总等待时间(总等
待时间的定义和 一样)。
最后要处理的就是从枚举的这 段到后面的某一个关键点之间的转移,这个把式子列出来之后可以
发现用斜率优化就能直接优化到 了。
总时间复杂度为 。
出题人说:数据造的我想吐。
蒟蒻说:代码打的我想吐。

AC Code:

#include<bits/stdc++.h>
#define maxn 3005
#define LL long long
using namespace std;

int n,tc[2];
LL sump[maxn][2],sumt[maxn][2];

struct node{
	int L,R,t,r,tp;
}a[maxn],b[maxn];
struct type_one_node{
	int L,R,r,tp;
	LL dp;
}c[maxn];
LL dp[maxn];

inline bool cmp1(const node &A,const node &B){ return A.t<B.t; }
inline bool cmp2(const node &A,const node &B){ return A.r<B.r; }

int q[maxn];
LL x[maxn],y[maxn];

int main(){
	int T;
	for(scanf("%d",&T);T--;){
		LL ans = 1ll<<60;
		scanf("%d%d%d",&n,&tc[0],&tc[1]);
		for(int i=1;i<=n;i++)
			scanf("%d%d",&a[i].tp,&a[i].t),a[i].r=a[i].t+tc[--a[i].tp]; 
		sort(a+1,a+1+n,cmp1);
		for(int i=1;i<=n;i++) b[i] = a[i];
		for(int i=1,j=1;i<=n;i++){
			for(;j<=n && b[j].t <= a[i].t;j++);
			a[i].L = j-1;
		}
		sort(a+1,a+1+n,cmp2);
		for(int i=1,j=1;i<=n;i++){
			for(;j<=n && b[j].t <= a[i].r;j++);
			a[i].R = j-1;
		}
		for(int i=1;i<=n;i++)
			sump[i][0] = sump[i-1][0] , sump[i][1] = sump[i-1][1],
			sumt[i][0] = sumt[i-1][0] , sumt[i][1] = sumt[i-1][1],
			sump[i][b[i].tp] ++ , sumt[i][b[i].tp] += b[i].t;
		for(int i=1;i<=n;i++){
			dp[i] = sump[a[i].R][a[i].tp^1] * a[i].r - sumt[a[i].R][a[i].tp^1];
		}
		for(int s=1;s<=n;s++){
			int m = 1;
			c[m].L=a[s].L,c[m].R=a[s].R,c[m].r=a[s].r,c[m].tp=a[s].tp;
			for(;sump[c[m].R][c[m].tp^1]^sump[c[m-1].L][c[m].tp^1];){
				m++;
				c[m].L = c[m-1].R , c[m].R = c[m].L , c[m].tp = c[m-1].tp^1 , c[m].r = c[m-1].r + tc[c[m].tp];
				for(;c[m].R <= n && b[c[m].R].t <= c[m].r;c[m].R++);
				c[m].R--;
			}
			c[1].dp=dp[s];
			for(int i=2;i<=m;i++){
				c[i].dp = c[i-1].dp + (sump[c[i].R][c[i].tp^1] - sump[c[i-1].L][c[i].tp^1]) * c[i].r - sumt[c[i].R][c[i].tp^1] + sumt[c[i-1].L][c[i].tp^1];
				if(sump[c[i].L][c[i].tp] == sump[n][c[i].tp]) ans = min(ans , c[i].dp);
			}
			for(int tp=0;tp<2;tp++)
				for(int i=s+1,j=1,L=0,R=0;i<=n;i++){
					if(a[i].tp==tp) continue;
					for(;j<=m && c[j].r<=a[i].t;j++)
						if(c[j].tp==tp){
							x[j] = sump[c[j].L][c[j].tp] , y[j] = sumt[c[j].L][c[j].tp] + c[j].dp;
							for(;L<R-1 && (x[q[R-1]]-x[q[R-2]])*(y[j]-y[q[R-2]])-(x[j]-x[q[R-2]])*(y[q[R-1]]-y[q[R-2]]) <= 0;R--);
							q[R++] = j;
 						}
 					for(;L<R-1 && (x[q[L+1]]-x[q[L]])*a[i].r-(y[q[L+1]]-y[q[L]]) >= 0;L++);
 					if(L<R)
					 	dp[i] = min(dp[i],c[q[L]].dp + (sump[a[i].R][c[q[L]].tp] - sump[c[q[L]].L][c[q[L]].tp]) * a[i].r - sumt[a[i].R][c[q[L]].tp] + sumt[c[q[L]].L][c[q[L]].tp]);
				}
		}
		for(int i=1;i<=n;i++){
			if(sump[a[i].L][a[i].tp] == sump[n][a[i].tp]) 
				ans = min(ans , dp[i]);
		}
		printf("%lld\n",ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值