CF1741E Sending a Sequence Over the Network

Description

先水了发翻译

你现在有一个序列 \(a\),定义一个用该序列生成新序列 \(b\) 的规则如下:

  • \(a\) 这个序列分成连续的几段;

  • 对于每一段,我们把这一段的长度插入到这一段的右边左边。

  • 每一段进行操作后便得到了 \(b\) 序列。

比如 \(a\) = \([1,2,3,1,2,3]\) ,我们可以把其分成 \(\color {red}[1]\)\(\color {blue}[2,3,1]\)\(\color {green}[2,3]\),长度分别为 \(1\)\(3\)\(2\)

然后我们把长度随意插入原序列中,可以得到一个不唯一的 \(b\) 序列,例如:\(b = [\color {black}{1,}\color {red}1\color {black},3,\color {blue}{2,3,1},\color {black}2,\color {green}{3,2}]\)

现在给定一个长度为 \(n\) \((1≤n≤2 \times 10^5)\) 已经操作后的序列 \(b\) \((1\le b_i \le 10^9)\),询问你是否能构造出任意一个原序列 \(a\),使得它进行如上操作后可以得到 \(b\),若能构造出输出 YES,否则输出 NO

共有 \(t\) \((1 \le t \le 10^4)\) 组数据,\(\sum n \le 2 \times 10^5\)

请注意常数可能造成的影响,否则可能会出现 TLE 等评测结果。

Solution

发现直接扫不好得到答案,同时题目只询问你是否能构造出,可以考虑 DP。

f[i][0/1] 表示已经考虑到了第 \(i\) 个位置,这个位置必为某一段的长度,放在开头/末尾否可行。

考虑对两种情况分别进行转移。以下例子中,() 表示现在 \(i\) 的位置

  • 考虑这一位必为某段开头的情况:

    • 如果它前面那一段也是长度在开头位置,例如 \([\color {black}3,\color {blue}{2,3,1},\color {black}(2) ....]\) ,发现当我们之前计算到 \(3\) 时,就已经可以转移出这个状态的可行性,这个状态就不用考虑了;

    • 如果它前面那一段是长度在末尾位置,例如 \([\color {blue}{2,3,1},\color {black}3,(2) ....]\) ,则我们只需要判断 \(i - 1\) 的位置必为末尾这个状态是否可行即可。

如果这个状态 f[i][0] = 1 ,我们可以去考虑它能向后更新哪个状态(其实就是第一种情况的转移,只不过是某个状态向后转移得到的)。

既然这是长度,说明后面 \([i + 1, i + b_i]\) 这一段都归它了,下一个段只能从 \(i + b_i + 1\) 开始,若就拿 \(b_{i+b_i+1}\) 为某段开头,可以得出 f[i+b[i]+1][0] = 1 \((i + b[i] + 1 \le n + 1)\)

  • 考虑这一位必为某段末尾的情况:

    • 如果它前面那一段是长度在开头位置可行,例如 \([\color {black}3,\color {blue}{2,3,1},\color {green}{3,2}, \color {black} {(2)} .... ]\),则根据上面转移可知,以 \(i - a[i]\) 位置为开头的状态为开头应当也是可行的(在这个例子中就是 \(\color {green} 3\) 为开头的状态可行),直接判断 f[i - a[i]][0] 的可行性即可。

    • 如果它前面那一段是长度在末尾位置可行,例如 \([\color {blue}{2,3,1},\color {black}3,\color {green}{3,2}, \color {black} {(2)} .... ]\),则根据上面转移同理可知,也是直接判断 f[i - a[i]][0] 的可行性即可。

所以这个大情况可以合并为判断 f[i - a[i]][0] 的可行性,即 f[i][1] = f[i - a[i]][0] \((i - a[i] \ge 1)\)

考虑获取最终答案,如果最后一段是开头位置为长度,那么可知 f[n+1][0] = 1;如果最后一段是末尾位置为长度,可知 f[n][1] = 1 所以最后判断这两个状态是否有至少一个可行即可。

关于初值设置,第一个数为开头状态肯定可行,即 f[1][0] = 1,同时也可知道 f[1 + a[i] + 1][0] = 1

由于数据组数过多,不能使用 memset 来清空数组,且需要注意 \(n + 1\) 的 DP 数组也需要清空

Code

思路中的细节详见代码。

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#include <map>
#define REP(i, x, y) for(int i = x; i < y; i++)
#define rep(i, x, y) for(int i = x; i <= y; i++)
#define PER(i, x, y) for(int i = x; i > y; i--)
#define per(i, x, y) for(int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
const int N = 200005;
int T;
int n, a[N], f[N][3];
int main()
{
	cin >> T;
	while(T--)
	{
		cin>>n;
		rep(i,1,n)
		{
			cin >> a[i];
			f[i][1] = f[i][0] = 0;
		}
		f[n + 1][1] = f[n + 1][0] = 0;// n + 1 位置也需要清空
		f[1][0] = 1;
		if(a[1] + 2 <= n + 1)
			f[a[1] + 2][0]=1;//初值
		for(int i = 2; i <= n; i++)
		{
            //这一位必为某段开头的情况的转移
			if(f[i-1][1] == 1) f[i][0] = 1;
			if(f[i][0] == 1)
			{
				if(i + a[i] + 1 <= n + 1)//注意边界
					f[i + a[i] + 1][0] = 1;
			}
            //这一位必为某段末尾的情况的转移
			if(i - a[i] >= 1 && f[i - a[i]][0] == 1)//同样,注意边界
			{
				f[i][1] = 1;
			}
		}
		if(f[n][1] == 1 || f[n + 1][0] == 1)
		{
			puts("YES");
			continue;
		}
		else
		{
			puts("NO");
		}
	}
	return 0;
}

posted @ 2022-10-19 20:54  panjx  阅读(47)  评论(0编辑  收藏  举报