【贪心+线段树维护DP】AGC001F Train Service Planning

【题目】
Atcoder
题目有点复杂。

n + 1 n+1 n+1个车站编号为 0 ∼ n 0\sim n 0n,以及 n n n条轨道连接第 i − 1 i-1 i1和第 i i i个车站,通过它要花费 a i a_i ai的时间。同时轨道可能是单向或双向的,双向可以同时允许两个方向列车行驶,单向在一个时间只允许一辆车在上面运行(没有车时可以改变方向)

现在要求运行从 0 0 0 n n n的列车和从 n n n 0 0 0的列车,满足(以下以前者为栗):

  • 如果它在 t t t时刻离开第 i − 1 i-1 i1个站台,则会在第 t + a i t+a_i t+ai的时刻到达 i i i站台。
  • 如果它在 s s s时刻到达 i i i站台并在 t t t时刻离开,那么下一列车会在 s + K s+K s+K时刻到达 i i i站台并在 t + K t+K t+K时刻离开。(即以 K K K为一次循环。
  • 任意时刻不能有两辆相向而行的列车在单向轨道内互相穿过。

最小化两个方向的火车从起点到终点的时间和。
n ≤ 1 0 5 , K , a i ≤ 1 0 9 n\leq 10^5,K,a_i\leq 10^9 n105,K,ai109

【解题思路】
一看就很神的一道题。
首先我们就没什么思路,不妨考虑将所有的限制条件先写出来:

q i q_i qi为正向每站等待时间, p i p_i pi为逆向,由于整个过程都是可以放在模 K K K意义下去做:
t i m 1 = ∑ i = 0 n − 1 q i + a i , t i m 2 = ∑ i = 0 n − 1 − ( p i + a i ) tim_1=\sum_{i=0}^{n-1}q_i+a_i,tim_2=\sum_{i=0}^{n-1}-(p_i+a_i) tim1=i=0n1qi+ai,tim2=i=0n1(pi+ai)
走到一个点 j j j的时间就是:
t i m 1 , j = ∑ i = 0 j − 1 q i + a i , t i m 2 , j = ∑ i = 0 j − 1 − ( p i + a i ) tim_{1,j}=\sum_{i=0}^{j-1}q_i+a_i,tim_{2,j}=\sum_{i=0}^{j-1}-(p_i+a_i) tim1,j=i=0j1qi+ai,tim2,j=i=0j1(pi+ai)
于是对于一个 j − 1 → j j-1\rightarrow j j1j的轨道限制,其运行区间就是:
[ q j − 1 + ( ∑ i = 0 j − 2 q i + a i ) , ∑ i = 0 j − 1 q i + a i ] [q_{j-1}+(\sum_{i=0}^{j-2}q_i+a_i) , \sum_{i=0}^{j-1}q_i+a_i] [qj1+(i=0j2qi+ai),i=0j1qi+ai]
逆向同理。

上面这个放在模 K K K意义下后变成前缀和取负的操作十分需要体会。总之我们对上面的限制进行整理与化简,大概是将一个端点不能在另一个区间里作为一条不等式,这样会有两条不等式四个不等号,最终我们可以得到:
∑ i = 0 j ( p i + q i ) ∉ [ − 2 ⋅ s j , − 2 ⋅ ( s j − a j ) ] \sum_{i=0}^j (p_i+q_i)\notin [-2\cdot s_j,-2\cdot (s_j-a_j)] i=0j(pi+qi)/[2sj,2(sjaj)]
其中 s s s a a a的前缀和,这个东西无解的条件就是 2 ⋅ a i ≥ K 2\cdot a_i\ge K 2aiK

我们最后要求的实际上是最小化 ∑ q + ∑ p + 2 ⋅ ∑ a \sum q +\sum p+2\cdot \sum a q+p+2a,那么只用最小化前两个就行了,当然在这里因为柿子只与和有关,它们可以看作一个值。

有了上面的不等式,再由于我们是在模 K K K意义下,可以把前一段补到后面去,我们的问题就变成了给定 n n n个区间,任选起点,走 n n n步,第 i i i步需要落在第 i i i个区间中,求最小总路径长度。
即形如:
S i ∈ [ l i , r i ] S_i\in [l_i,r_i] Si[li,ri]
然后使得 S n − 1 − S 0 S_{n-1}-S_0 Sn1S0最小。

观察到每次到下一个区间的限制,我们要么留在原地不动,要么走到下一个区间的左端点位置,而如果我们走到了一个左端点,接下来的路径是一定的。不妨倒着维护这个过程,定义 f i f_i fi表示对于当前区间 [ l i , r i ] [l_i,r_i] [li,ri]的左端点 l i l_i li而言,往后走到第 n − 1 n-1 n1个区间的最小长度。假设当前我们做完了第 i i i个区间,我们可以将 [ l i , r i ] [l_i,r_i] [li,ri]这段区间的补集覆盖成 i i i。推 f i f_i fi时,假设 l i l_i li位置上有一个值 j j j,说明编号在 [ i , j − 1 ] [i,j-1] [i,j1]中的所有区间都覆盖了这个点,那么我们可以原地不动直到第 j j j个区间,此时我们再移动过去即可,转移就是 f i = f j + d i s ( l i , l j ) f_i=f_j+dis(l_i,l_j) fi=fj+dis(li,lj)

最后枚举起点算一下答案即可,复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),当然需要离散化辣。

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=3e5+10;
int n,K,cnt;
int a[N],b[N],c[N<<1],l[N],r[N];
ll ans,s[N],f[N];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

struct Segment
{
#define ls (x<<1)
#define rs (x<<1|1)
	int col[N<<2],tar[N<<2];
	void pushdown(int x)
	{
		if(!tar[x]) return;
		col[ls]=col[rs]=tar[ls]=tar[rs]=tar[x];tar[x]=0;
	}
	void update(int x,int l,int r,int L,int R,int v)
	{
		if(L>R) return;
		if(L<=l && r<=R){col[x]=tar[x]=v;return;}
		pushdown(x);
		int mid=(l+r)>>1;
		if(L<=mid) update(ls,l,mid,L,R,v);
		if(R>mid) update(rs,mid+1,r,L,R,v);
	}
	int querycol(int x,int l,int r,int p)
	{
		if(l==r) return col[x];
		pushdown(x);
		int mid=(l+r)>>1;
		if(p<=mid) return querycol(ls,l,mid,p);
		else return querycol(rs,mid+1,r,p);
	}
	ll query(int x)
	{
		int pos=querycol(1,1,cnt,x);
		return pos?f[pos]+(c[l[pos]]-c[x]+K)%K:0;
	}
#undef ls
#undef rs
}tr;

int main()
{
#ifdef Durant_Lee
	freopen("AGC011F.in","r",stdin);
	freopen("AGC011F.out","w",stdout);
#endif
	n=read();K=read();
	for(int i=1;i<=n;++i)
	{
		a[i]=read();b[i]=read();s[i]=s[i-1]+a[i];
		if(b[i]&1) l[i]=(K-s[i-1]*2%K)%K,r[i]=(K-s[i]*2%K)%K;
		else l[i]=0,r[i]=K-1;
		if(b[i]&1 && a[i]*2>K){puts("-1");return 0;}
		c[++cnt]=l[i];c[++cnt]=r[i];
	}
	sort(c+1,c+cnt+1);cnt=unique(c+1,c+cnt+1)-c-1;
	for(int i=1;i<=n;++i) l[i]=lower_bound(c+1,c+cnt+1,l[i])-c,r[i]=lower_bound(c+1,c+cnt+1,r[i])-c;
	for(int i=n;i;--i)
	{
		f[i]=tr.query(l[i]);
		if(l[i]>r[i]) tr.update(1,1,cnt,r[i]+1,l[i]-1,i);
		else tr.update(1,1,cnt,1,l[i]-1,i),tr.update(1,1,cnt,r[i]+1,cnt,i);
	}
	ans=f[1];
	for(int i=cnt;i;--i) ans=min(ans,tr.query(i));
	printf("%lld\n",ans+2*s[n]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值