CF1342F Make It Ascending

CF1342F Make It Ascending

题目大意

给定一个长度为 n n n的序列 a a a,每次可以选择两个位置 i , j ( i ≠ j ) i,j(i\not=j) i,j(i=j),令 a j = a i + a j a_j=a_i+a_j aj=ai+aj并将 a i a_i ai从序列中删除
求将原序列变成严格单调上升序列的最少操作次数
n ≤ 15 n\le15 n15


题目相当于:求将序列 a a a划分成若干集合 S 1 , S 2 , ⋯   , S c S_1,S_2,\cdots,S_c S1,S2,,Sc,集合之间有序,满足 S i S_i Si之和小于 S i + 1 S_{i+1} Si+1之和(即单调递增),且存在一个单调上升序列 p p p满足 p i ∈ S i p_i \in S_i piSi,即我们最终会将 S i S_i Si所有其他元素累和到原序列位置为 p i p_i pi的元素上 求最多划分出多少集合

朴素的想法是将所有限制全部装进 D P DP DP里面,即设 f i , j , k f_{i,j,k} fi,j,k表示当前考虑了编号为 i i i的数集,最后一个划分出的集合对应的 p p p的最小值为 j j j,且总和为 k k k时,最多划分出多少集合

转移根据实际意义显然

这样复杂度太大了 因为我们把值域装了进去 考虑如何把值域去掉

由于若 k > k ′ k>k^{'} k>k f i , j , k ≤ f i , j , k ′ f_{i,j,k} \le f_{i,j,k^{'}} fi,j,kfi,j,k则前者显然无用

这说明对于相同的 f i , j , k f_{i,j,k} fi,j,k,仅有最小的 k k k有用

根据这一性质,我们尝试忽略无用状态,交换 D P DP DP的某一维度和值域

这意味着,我们设 f i , j , k f_{i,j,k} fi,j,k表示当处于上述意义下的 i , j i, j i,j以及集合个数 k k k时,最后一个集合的和的最小值

转移即在 i i i这一维枚举子集,同时枚举 j , k j,k j,k

转移条件有三个:

  1. ∑ p ∈ S a p > f i , j , k \sum_{p \in S} a_p > f_{i,j,k} pSap>fi,j,k
  2. ∃ p ∈ S s.t. p > j \exists p \in S \text{s.t.} p >j pSs.t.p>j p m i n p_{min} pmin为最小的这样的 p p p
  3. i ∩ S = ∅ i \cap S = \varnothing iS=

f i , j , k f_{i,j,k} fi,j,k可以转移到 f i ∪ S , p m i n , k + 1 f_{i \cup S,p_{min},k+1} fiS,pmin,k+1,并令后者对 ∑ p ∈ S a p \sum_{p \in S}a_p pSap m i n min min

容易发现 时间复杂度为 O ( 3 n n 2 ) \mathcal{O} (3^n n^2) O(3nn2)

由于不合法状态较多 故 ✓ \checkmark


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

const int N = 16;
struct dat {int msk, num, pos;} tr[1 << N][N][N];
int T, n, a[N], ban[N], s[1 << N], f[1 << N][N][N];
vector <pair <int, int>> ans;
int find(int x) {
	int rnk = 1;
	for(int i = 1; i < x; i++) if(!ban[i]) rnk++;
	return rnk;
}
void calc(int msk, int num, int pos) {
	if(!msk) return;
	dat t = tr[msk][num][pos];
	calc(t.msk, t.num, t.pos);
	for(int i = 0; i < n; i++)
		if(msk - t.msk >> i & 1 && i != pos - 1)
			ans.push_back({i + 1, pos});
}
void solve() {
	cin >> n, ans.clear();
	for(int i = 0; i < n; i++) cin >> a[i];
	for(int i = 0; i < 1 << n; i++)
		for(int j = 0; j <= n; j++)
			for(int k = 0; k <= n; k++)
				f[i][j][k] = 1e9;
	f[0][0][0] = 0;
	for(int i = 1; i < 1 << n; i++)
		for(int j = 0; j < n; j++)
			if(i >> j & 1)
				s[i] = s[i - (1 << j)] + a[j];
	for(int i = 0; i < 1 << n; i++)
		for(int j = 0; j < n; j++)
			for(int k = 0; k < n; k++)
				if(f[i][j][k] < 1e9) {
					int C = (1 << n) - 1 - i;
					for(int S = C; S; S = (S - 1) & C)
						if(s[S] > f[i][j][k] && S >> k) {
							int nk = __builtin_ctz(S >> k << k) + 1;
							if(s[S] >= f[i + S][j + 1][nk]) continue;
							f[i + S][j + 1][nk] = s[S];
							tr[i + S][j + 1][nk] = {i, j, k};
						}
				}
	for(int num = n; num; num--)
		for(int pos = 0; pos <= n; pos++)
			if(f[(1 << n) - 1][num][pos] != 1e9) {
				calc((1 << n) - 1, num, pos);
				cout << ans.size() << endl;
				memset(ban, 0, sizeof(ban));
				for(auto it : ans) {
					cout << find(it.first) << " " << find(it.second) << endl;
					ban[it.first] = 1;
				}
				return;
			}
}
int main() {
	cin >> T;
	while(T--) solve();
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值