CF #826 (Div. 3) - D(思维,模拟),E(DP)

E. Sending a Sequence Over the Network

题意
一个数列 a 可以构成若干个数列 b:

  • 把数列 a 分成几段;
  • 对于每一段,把其长度写到这一段的左边或者右边位置,构成数列 b。

例如把数列 a = [ 1 , 2 , 3 , 1 , 2 , 3 ] a = [1, 2, 3, 1, 2, 3] a=[1,2,3,1,2,3] 分成三段 [ 1 ] + [ 2 , 3 , 1 ] + [ 2 , 3 ] [\color{red}{1}\color{black}] + \color{black}[\color{blue}{2, 3, 1}\color{black}] + [\color{green}{2, 3}\color{black}] [1]+[2,3,1]+[2,3],可以构成以下四种数列 b:
b = [ 1 , 1 , 3 , 2 , 3 , 1 , 2 , 3 , 2 ] b = [\color{black}1, \color{red}{1},\color{black}3, \color{blue}{2, 3, 1}, \color{green}{2, 3}, \color{black}2] b=[1,1,3,2,3,1,2,3,2]
b = [ 1 , 1 , 3 , 2 , 3 , 1 , 2 , 2 , 3 ] b = [\color{red}{1}, \color{black}1, 3, \color{blue}{2, 3, 1}, \color{black}2, \color{green}{2, 3}\color{black}] b=[1,1,3,2,3,1,2,2,3]
b = [ 1 , 1 , 2 , 3 , 1 , 3 , 2 , 2 , 3 ] b = [\color{red}{1}, \color{black}1, \color{blue}{2, 3, 1}, \color{black}3, 2, \color{green}{2, 3}\color{black}] b=[1,1,2,3,1,3,2,2,3]
b = [ 1 , 1 , 2 , 3 , 1 , 3 , 2 , 3 , 2 ] b = [\color{red}{1}, \color{black}1,\color{blue}{2, 3, 1}, \color{black}3, \color{green}{2, 3}, \color{black}2] b=[1,1,2,3,1,3,2,3,2].

现在给出数列 b,判断其是否可以由一个数列 a 得到?

1 ≤ n ≤ 2 ⋅ 1 0 5 1 \le n \le 2 \cdot 10^5 1n2105

思路
情况太复杂,考虑dp。

定义状态 f[i] 表示前 i 个位置是否合法。

对于当前 i 位置:

  • 如果其作为每段右边的长度标志的话,那么可以从 i-a[i]-1 位置来转移。如果前 i-a[i]-1 个位置是合法的,那么前 i 个位置就合法。f[i] = max(f[i], f[i-a[i]-1];
  • 如果其作为每段左边长度标志的话,那么可以对 i+a[i] 位置做贡献。如果前 i-1 个位置合法的话,那么前 i+a[i] 个位置就合法。f[i+a[i]] = max(f[i+a[i]], f[i-1]);
  • 如果其不作为长度标志的话,那就应该往前遍历看哪一个是左边长度标志,从那个位置的前一个位置 x 来转移。但这样的时间复杂度就 n^2 了,显然不行。又转念一想,之前遍历到那个位置 x 的时候,其由于第二种操作已经把当前位置更新了,所以完全不用考虑这种情况。只考虑上述两种情况即可。
#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], f[N];

signed main(){
	Ios;
	cin >> T;
	while(T--)
	{
		cin >> n;
		for(int i=1;i<=n;i++) cin >> a[i], f[i] = 0;
		f[0] = 1;
		
		for(int i=1;i<=n;i++)
		{
			if(i >= a[i] + 1) f[i] = max(f[i], f[i-a[i]-1]); //当前位置作为右边长度标志 
			if(i + a[i] <= n) f[i + a[i]] = max(f[i + a[i]], f[i-1]); //当前位置作为左边长度标志,对后面的位置产生贡献 
		}
		if(f[n]) cout << "YES\n";
		else cout << "NO\n";
	}
	return 0;
}

幸好下午看了几篇 dp 博客,能够想到用 dp ~


D. Masha and a Beautiful Tree

题意
给定一个长度为 n 的全排列,保证 n 为 2 的幂次。
分别表示为一个深度为 n 的完全二叉树的叶子节点的点权。
在这里插入图片描述

每次操作可以选择一个节点,然后互换其左右子树。

问,最少需要多少次操作,能把整个全排列变成升序?不合法输出 -1。

1 ≤ n ≤ 262144 ,保证为 2 的幂次 1 \le n \le 262144,保证为 2 的幂次 1n262144,保证为2的幂次

思路
贪心,先把小子树的顺序改变好,然后去改变大子树的顺序。
先两个两个考虑,如果不是升序的话,互换一次变成升序。
然后把两个合并成为一个,下标为偶数的那个节点/2,然后再两个两个考虑,如果不是升序的话,互换一次变成升序。
一直合并,直到合并成一个。

如果发现考虑的两个不是相邻数字的话,那就不合法。

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

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 300010, mod = 1e9+7;
int T, n, m;
int a[N], b[N];

signed main(){
	Ios;
	cin >> T;
	while(T--)
	{
		cin >> n;
		
		for(int i=1;i<=n;i++) cin >> a[i];
		
		if(n == 1){cout << 0 << endl; continue;}
		
		int flag = 0, cnt = 0;
		while(1)
		{
			for(int i=1;i<=n;i+=2)
			{
				if(abs(a[i] - a[i+1]) != 1){
					flag = 1; break;
				}
				if(a[i] == a[i + 1] + 1) cnt ++, swap(a[i], a[i + 1]);
			}
			if(flag) break;
			
			int t = 0;
			for(int i=1;i<=n;i+=2)
			{
				t ++;
				b[t] = a[i+1]/2;
			}
			n = t;
			for(int i=1;i<=n;i++) a[i] = b[i];
			if(n == 1) break;
		}
		if(flag) cout << - 1 << endl;
		else cout << cnt << endl;
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值