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 n≤15
题目相当于:求将序列 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 pi∈Si,即我们最终会将 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,k≤fi,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
转移条件有三个:
- ∑ p ∈ S a p > f i , j , k \sum_{p \in S} a_p > f_{i,j,k} ∑p∈Sap>fi,j,k
- ∃ p ∈ S s.t. p > j \exists p \in S \text{s.t.} p >j ∃p∈Ss.t.p>j 设 p m i n p_{min} pmin为最小的这样的 p p p
- i ∩ S = ∅ i \cap S = \varnothing i∩S=∅
则 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} fi∪S,pmin,k+1,并令后者对 ∑ p ∈ S a p \sum_{p \in S}a_p ∑p∈Sap取 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;
}