CF1500F Cupboards Jumps

CF1500F Cupboards Jumps

给定正整数 \(n,C\) 和长度为 \(n-2\) 的序列 \(w(0\leq w_i\leq C)\)
你需要构造一个长度为 \(n\) 的序列 \(h\),满足:
1.对于任意整数 \(i\in[1,n-2]\)\(\max(h_i,h_{i+1},h_{i+2})-\min(h_i,h_{i+1},h_{i+2})=w_i\)
2.序列 \(h\) 中的任意元素 \(h_i\) 满足 \(0\leq h_i\leq 10^{18}\)
有解输出 YES 之后输出任意一个满足要求的序列 \(h\),无解输出 NO
对于 \(100\%\) 的数据,满足 \(3\leq n\leq10^6,0\leq C\leq10^{12}\)

绝世好题。

首先考虑差分,令 \(d_i=h_{i+1}-h_i\),可以发现 \(w_i=\max \{|d_i|,|d_{i+1}|,|d_i+d_{i+1}|\}\)
不难发现 \(w_i=|d_i|+|d_{i+1}|\) 当且仅当 \(d_i\)\(d_{i+1}\) 同正同负。这就意味着,如果 \(d_i\) 的一个连续段都是同正或同负的,那么只要确定一个就能确定所有;反之,如果 \(d_i\)\(d_{i+1}\) 异号,那么必然有一个绝对值为 \(w_i\)

考虑对差分进行 DP,令 \(f_{i,j}\) 表示前 \(i\) 个数,将 \(j\) 作为结尾是否可行。转移如下:

\[f_{i+1,w_i}←f_{i,x}\ (x\leq w_i) \]

\[f_{i+1,x}←f_{i,w_i}\ (x\leq w_i) \]

\[f_{i+1,x}←f_{i,w_i-x}\ (x\leq w_i) \]

时间复杂度 \(\mathcal{O}(nC)\),考虑优化。

对每一种转移推一波性质:

  • \(f_{i+1,w_i}←f_{i,x}\ (x\leq w_i)\),如果上一层存在 \([x,x](x\leq w_i)\),则在这一层加入区间 \([w_i,w_i]\)

  • \(f_{i+1,x}←f_{i,w_i}\ (x\leq w_i)\),如果上一层存在 \([w_i,w_i]\),则在这一层加入 \([0,w_i]\)

  • \(f_{i+1,x}←f_{i,w_i-x}\ (x\leq w_i)\),相当于把上一层的区间以 \(\dfrac{w_i}{2}\) 为中心翻转,再计算一个单点的贡献。但是这样计算很麻烦,不妨考虑每次都对数轴以零点为中心进行翻转,只需额外记录当前翻转次数和平移距离即可。

对于翻转操作,我们考虑使用 \(\texttt{STL::set}\),这样可以使时间复杂度变为 \(\mathcal{O}(n\text{log}_2n)\)

接下来考虑如何构造合法方案。

我们不妨首先找出任意一个 \(res_i\),表示这个值可以在 \(|d_i|\) 中出现,然后我们考虑如何构造出 \(|d_i|\),考虑从最后一个元素开始,我们令 \(|d_{n+1}|=res_{n+1}\),然后如果 \(|d_{n+1}|=w_n\),那么直接令 \(|d_n|=res_n\),否则如果 \(res_n=w_n\),那我们就令 \(|d_n|=w_n\),否则只好令 \(|d_n|=w_n-|d_{n+1}|\)。然后考虑如何通过 \(|d_i|\) 构造出 \(d_i\),具体方案就是从右向左扫一遍,如果 \(|d_i|+|d_{i+1}|≠w_i\),就将其变号。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define N 1000010
int n,m,a[N];
int res[N],del[N];
int L,R,sign,tag;
set <int> st;

int read ()
{
	int k=1,s=0;char ch=getchar ();
	while (!isdigit (ch)) {if (ch=='-') k=-1;ch=getchar ();}
	while (isdigit (ch)) {s=s*10+ch-'0';ch=getchar ();}
	return k*s;
}

void Init ()
{
	n=read (),m=read ();
	L=0,R=m;
	for (int i=1;i<=n-2;i++)
	{
		a[i]=read ();
		int limL,limR,now;
		if (sign) limL=tag,limR=tag-a[i];
		else limL=-tag,limR=a[i]-tag;
		now=limR;
		if (limL>limR) swap (limL,limR);
		L=max (L,limL),R=min (R,limR);
		while (st.size () && (*st.begin ())<limL)
			st.erase (st.begin ());
		while (st.size () && (*st.rbegin ())>limR)
			st.erase (*st.rbegin ());
		if (!st.size () && L>R)
		{
			printf ("NO\n");
			exit (0);
		}
		if (st.count (now) || (L<=now && now<=R))
		{
			L=0,R=a[i],res[i]=a[i];
			st.clear ();
			sign=0,tag=0;
			continue;
		}
		int pre=(L<=R)?L:*st.begin ();
		if (sign) res[i]=tag-pre;
		else res[i]=tag+pre;
		sign^=1,tag=a[i]-tag;
		if (sign) now=tag-a[i];
		else now=a[i]-tag;
		st.insert (now);
	}
}

void Work ()
{
	int pre=(L<=R)?L:*st.begin ();
	if (sign) del[n-1]=tag-pre;
	else del[n-1]=tag+pre;
	for (int i=n-2;i>=1;i--)
		if (res[i]==a[i]) del[i]=a[i];
		else if (del[i+1]==a[i]) del[i]=res[i];
		else del[i]=a[i]-del[i+1];
	int opt=1;
	for (int i=n-2;i>=1;i--)
	{
		if (abs (del[i])+abs (del[i+1])!=a[i]) opt*=-1;
		del[i]=opt*del[i];
	}
	int low=0;
	for (int i=1;i<=n-1;i++)
	{
		del[i]+=del[i-1];
		low=min (low,del[i]);
	}
	printf ("YES\n");
	for (int i=1;i<=n;i++)
		printf ("%lld ",del[i-1]-low);
	printf ("\n");
}

signed main ()
{
	Init ();
	Work ();
	return 0;
}
posted @ 2023-12-25 13:34  Cyber_Punk  阅读(82)  评论(0)    收藏  举报