Educational CF Round 86___F. Make It Ascending —— 子集dp

题目链接:点我啊╭(╯^╰)╮
参考博客:点我啊╭(╯^╰)╮

题目大意:

     n n n 个数字,要求用最少的操作
    每次操作选择 i i i j j j a j + = a i a_j += a_i aj+=ai
    最后生成单调递增的序列
    并输出过程

解题思路:

    可以看出是分组选,每一组合并成一个值
     d p [ i ] [ j ] [ z ] dp[i][j][z] dp[i][j][z] 表示 到第 i i i 组为止,选取的状态为 j j j,最右边的值位置为 z z z 的最小值
    从 d p [ i ] [ j ] [ z ] dp[i][j][z] dp[i][j][z] d p [ i + 1 ] dp[i+1] dp[i+1] 转移, i + 1 i+1 i+1可选的状态即为 ( ( 1 < < n ) − 1 ) ⨁ j ( (1<<n) - 1 ) \bigoplus j ((1<<n)1)j 的所有子集
    并且 d p [ i + 1 ] dp[i+1] dp[i+1] z z z 值一定要比 d p [ i ] dp[i] dp[i] z z z 值大
    因此枚举所有子集就可以求出所有最小值

    最后的答案肯定是从组数最多,并且 z z z 值最大 的那种情况
    时间复杂度: O ( n 2 × 3 n ) O(n^2 \times 3^n) O(n2×3n)

#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;
int T, n, a[17], sum[1<<16];
int dp[17][1<<16][17];
pii op[17][1<<16][17];
 
signed main() {
	scanf("%d", &T);
	while(T--){
		scanf("%d", &n);
		for(int i=0; i<n; i++) scanf("%d", a+i);
		for(int i=0; i<=n; i++)
			for(int j=0; j<1<<n; j++)
				for(int z=0; z<=n; z++)
					sum[j] = 0, dp[i][j][z] = 2e9;
		for(int s=1; s<1<<n; s++)
			for(int i=0; i<n; i++)
				if((s >> i) & 1) sum[s] += a[i];
		
		dp[0][0][0] = 0;
		for(int i=1; i<=n; i++)
			for(int j=0; j<1<<n; j++)
				for(int z=0; z<n; z++){
					int s = (1 << n) - 1 ^ j;
					if(dp[i-1][j][z] > 1e9) continue;
					
					for(int k=s; k; k=(k-1)&s){
						if(sum[k] <= dp[i-1][j][z]) continue;
						if(k >> z == 0) continue;
						
						int nz = z + __builtin_ffs(k>>z);
						if(dp[i][j|k][nz] > sum[k]){
							dp[i][j|k][nz] = sum[k];
							op[i][j|k][nz] = {j, z};
						}
					}
				}
		
		int a = -1, b = (1 << n) - 1, c = -1;
		for(int i=n; i; i--){
			for(int j=n; j; j--)
				if(dp[i][b][j]<1e9){
					c = j; break;
				}
			if(c >= 0) {
				a = i; break;
			}
		}
		vector <pii> ans;
		for(int i=a; i; i--){
			int nb = b ^ op[i][b][c].first;
			int nc = op[i][b][c].second;
			for(int j=0; j<n; j++)
				if(((nb >> j) & 1) && j != c - 1)
					ans.push_back({j, c-1});
			b = op[i][b][c].first, c = nc;
		}
		printf("%d\n", ans.size());
		for(int i=0; i<ans.size(); i++){
			int x = ans[i].first, y = ans[i].second;
			for(int j=0; j<i; j++) if(ans[j].first < ans[i].first) x--;
			for(int j=0; j<i; j++) if(ans[j].first < ans[i].second) y--;
			printf("%d %d\n", x + 1, y + 1);
		}
	}; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值