[CF1523C] Compression and Expansion (DP/贪心)

C. Compression and Expansion

题面


一个合法的表单由横向 N N N 行数字链,纵向一层或多层数字链组成,第 k k k 层的数字链(可以想象为前面打了 k k k 个制表符 )由 k k k 个数字组成,如 k = 3 : 1.1.1 , 1.1.2 , 1.2.1 , ⋯ k=3:1.1.1,1.1.2,1.2.1,\cdots k=3:1.1.1,1.1.2,1.2.1, k = 1 : 1 , 2 , 3 , ⋯ k=1:1,2,3,\cdots k=1:1,2,3,。每个形如 a 1 . a 2 . a 3 . ⋯   . a k . x a_1.a_2.a_3.\cdots.a_k.x a1.a2.a3..ak.x 的数字链都会在数字链 a 1 . a 2 . a 3 . ⋯   . a k a_1.a_2.a_3.\cdots.a_k a1.a2.a3..ak 的后面,以及数字链 a 1 . a 2 . a 3 . ⋯   . ( a k + 1 ) a_1.a_2.a_3.\cdots.(a_k+1) a1.a2.a3..(ak+1)(如果有的话) 的前面,并且相互之间 x x x 按照从小到大的顺序(它们不一定相邻)。上图中的最左边就是一个合法例子,右边的不合法。

现在告诉你每一行的数字链的最后一个数字,要还原出任意一个合法的表单。

1 ≤ N ≤ 1000 1\leq N\leq 1000 1N1000.

题解

Dynamic Programming

先分析一下最后一个数字给我们的信息:

  • 如果此行是 1(最后数字),那么层数一定是上一行+1 。
  • 如果此行非 1 ,设其为 i i i,那么和之前的某个末尾为 i − 1 i-1 i1 的同层。

这种方向不好分析,我们换一换:

  • 如果此行的下一行是 1,那么此行层数一定为下一行-1 ,且设此行的(末尾)数字为 i i i ,后面可能有某数字为 i + 1 i+1 i+1 的行与此行最近同层。
  • 如果此行的下一行非 1,设此行数字为 i i i ,那么此行后面一定不能直接接层数+1的行了,且后面与此行最近同层的行数字一定不为 i + 1 i+1 i+1

这样好像更复杂了,但是我们可以依此想出一个 D P \rm DP DP 的方法:

  • 维护两个值 d p [ i ] , n e x t [ i ] dp[i],next[i] dp[i],next[i] ,依次表示 ( d p [ i ] dp[i] dp[i]:) i i i 个位置向后延伸的最远距离,满足 [ i , i + d p [ i ] − 1 ] [i,i+dp[i]-1] [i,i+dp[i]1] 之间的行层数都不小于 i i i 的层数,以及 ( n e x t [ i ] next[i] next[i]:) 满足 d p [ i ] dp[i] dp[i] 最大的情况下,后面与 i i i 最近同层的 a i + 1 a_i+1 ai+1 的位置,方便输出方案(没有就为 N + 1 N+1 N+1
  • 从后往前计算,如果此行 i i i 的下一行数字是 1,就令 dp[i]=dp[i+1]+1 ,然后枚举后面的所有 a a a 值为 a i + 1 a_i+1 ai+1 的行 j j j(实为枚举 n e x t [ i ] next[i] next[i] 的可能值),若 dp[j]+(j-i) > dp[i] ,说明更优,此时更新 dp[i]=dp[j]+(j-i) , next[i]=j
  • 如果此行 i i i 的下一行数字非 1,为 a i + 1 a_i+1 ai+1,那么只好接着它:dp[i]=dp[i+1]+1 , next[i]=i+1
  • 如果此行 i i i 的下一行数字非 1 且非 a i + 1 a_i+1 ai+1,那么没法向后延伸了,dp[i]=1 , next[i]=N+1
  • 既然我们直到了每一行的 n e x t [ i ] next[i] next[i] ,那么就好输出方案了。

这样虽然有点大材小用,但是复杂度还是最优的 O ( n 2 ) O(n^2) O(n2)

CODE

#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 1005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
#define SI set<int>::iterator
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
int n,m,i,j,s,o,k;
int a[MAXN];
int dp[MAXN],nx[MAXN];
vector<int> bu[MAXN];
void print(int l,int r,string ss) {
	if(l > r) return ;
	int p = l;
	while(p > 0 && p <= r) {
		cout<<ss<<a[p]<<endl;
		char s0[15]; sprintf(s0,"%d.",a[p]);
		int nn = nx[p];
		string s2 = ss + s0;
		print(p+1,min(r,nn-1),s2);
		p = nn;
	}
	return ;
}
int main() {
	int T = read();
	while(T --) {
		n = read();
		for(int i = 1;i <= n;i ++) a[i] = read(),bu[i].clear();
		dp[n] = 1;nx[n] = n+1;
		bu[a[n]].push_back(n);
		for(int i = n-1;i > 0;i --) {
			dp[i] = 1;nx[i] = n+1;
			if(a[i+1] == 1) {
				int le = dp[i+1],nm = a[i]+1;
				dp[i] = le+1;
				for(int jj = 0;jj < (int)bu[nm].size();jj ++) {
					int j = bu[nm][jj];
					if(j <= i+le+1 && dp[j]+(j-i) > dp[i]) {
						dp[i] = dp[j]+(j-i); nx[i] = j;
					}
				}
			}
			else if(a[i+1] == a[i]+1) {
				dp[i] = dp[i+1]+1; nx[i] = i+1;
			}
			bu[a[i]].push_back(i);
		}
		print(1,n,"");
	}
	return 0;
}

Greedy

要是先祭出贪心方法,估计没人会看动规了吧

我们维护一个栈,若上一行的层数为 k k k ,那么从栈顶到栈底依次是当前层数为 k k k 的最后一行、层数为 k − 1 k-1 k1 的最后一行、层数为 k − 2 k-2 k2 的最后一行……

从前往后加行,每加一行 i i i 就分类:

  • a i > 1 a_i>1 ai>1:在栈中依次弹出栈顶,直到找到数字等于 a i − 1 a_i-1 ai1 的那行 j j j,然后成为它的后继,令 next[j]=i ,然后用 i i i 替换 j j j
  • a i = 1 a_i=1 ai=1:加入栈顶。

这样为什么是正确的呢?

a i = 1 a_i=1 ai=1 的情况就不用说了吧,只能加入栈顶。 a i > 1 a_i>1 ai>1 时,没法增加层数了,那么一定得令栈中的某个数字等于 a i − 1 a_i-1 ai1 的行替换为 a i a_i ai,并且让该数上面的都弹出栈。那么为什么不最小化弹出栈的元素个数呢?这样后面能匹配到的机会就更多,一定是最优的。

z x y : 这 样 不 就   O ( n )   了 吗 ? \rm zxy:这样不就~O(n)~了吗? zxy: O(n) 
对 曰 : 输 出 是   O ( n 2 )   的 , 你 没 法 降 维 。 \rm 对曰:输出是~O(n^2)~的,你没法降维。 : O(n2) 

CODE

Perfect Solution by jiangly

#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int t;
    std::cin >> t;
    while (t--) {
        int n;
        std::cin >> n;
        std::vector<int> a(n);
        for (int i = 0; i < n; i++) {
            std::cin >> a[i];
        }
        std::vector<std::vector<int>> ans(n);
        ans[0] = {1};
        std::vector<int> stk{0};
        for (int i = 1; i < n; i++) {
            if (a[i] == 1) {
                ans[i] = ans[stk.back()];
                ans[i].push_back(1);
                stk.push_back(i);
            } else {
                while (ans[stk.back()].back() != a[i] - 1) {
                    stk.pop_back();
                }
                ans[i] = ans[stk.back()];
                ans[i].back()++;
                stk.back() = i;
            }
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < int(ans[i].size()); j++) {
                std::cout << ans[i][j] << ".\n"[j == int(ans[i].size()) - 1];
            }
        }
    }
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值