正题
题目链接:https://www.luogu.com.cn/problem/AT3949
题目大意
长度为LLL的坐标轴上,给出nnn个点,每个点xix_ixi需要购物tit_iti的时间,一辆车在0∼L0\sim L0∼L折返跑,求从000出发购物完回到000的最短时间。
n∈[1,3×105],L∈[1,109]n\in[1,3\times 10^5],L\in[1,10^9]n∈[1,3×105],L∈[1,109],输入的xix_ixi单调递增。
解题思路
挺奇妙的题目,WC2021WC2021WC2021讲课的题。
首先每个tit_iti先%\%%上一个2×L2\times L2×L。然后把那些2×L2\times L2×L加到答案里先,这些无可避免。
然后考虑一个点,如果从右边进只需要到达一次端点就视为左括号,如果从右边进只需要到达一次端点就视为右括号。
先默认每个点的贡献都是2×L2\times L2×L,显然一个左括号和一个右括号匹配可以减少2×L2\times L2×L的贡献,因为如果先走右边那个再来走左边那个,这样他们的贡献和就是2×L2\times L2×L。
而有些点既可以视为左又可以视为右,此时我们需要最大化匹配数。
其实还有一个性质,如果一个节点开始固定作为左括号,那么它后面的一定不会有固定作为右括号的(拿作为左右括号的条件看一下就能理解了)。所以不会有两个固定的括号匹配。
然后就可以直接贪心匹配了,时间复杂度O(n)O(n)O(n)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=3e5+10;
int n,len,x[N],t[N],l[N],r[N],ans;
int main()
{scanf("%d%d",&n,&len);for(int i=1;i<=n;i++)scanf("%d",&x[i]);for(int i=1;i<=n;i++)scanf("%d",&t[i]);for(int i=1;i<=n;i++){ans+=t[i]/(2*len);t[i]%=2*len;if(!t[i]){ans--;continue;}l[i]=(t[i]<=x[i]*2);r[i]=(t[i]<=(len-x[i])*2);}int lim=n,L=0,R=0;ans+=n+1-r[n];for(int i=1;i<n;i++){if(!l[i]&&!r[i])continue;if(!r[i]){lim=i;break;}if(!l[i]&&L)L--,ans--;else if(l[i]) L++;}for(int i=n-1;i>=lim;i--){if(!l[i]&&!r[i])continue;if(!l[i])break;if(!r[i]&&R)R--,ans--;else if(r[i]) R++;}ans-=(L+R)>>1;printf("%lld\n",2ll*ans*len);return 0;
}