[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
1≤N≤1000.
题解
Dynamic Programming
先分析一下最后一个数字给我们的信息:
- 如果此行是 1(最后数字),那么层数一定是上一行+1 。
- 如果此行非 1 ,设其为
i
i
i,那么和之前的某个末尾为
i
−
1
i-1
i−1 的同层。
这种方向不好分析,我们换一换:
- 如果此行的下一行是 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
k−1 的最后一行、层数为
k
−
2
k-2
k−2 的最后一行……
从前往后加行,每加一行
i
i
i 就分类:
- a
i
>
1
a_i>1
ai>1:在栈中依次弹出栈顶,直到找到数字等于
a
i
−
1
a_i-1
ai−1 的那行
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
ai−1 的行替换为
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
#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;
}
[CF1523C] Compression and Expansion (DP/贪心)的更多相关文章
- 【bzoj4027】[HEOI2015]兔子与樱花 树形dp+贪心
题目描述 很久很久之前,森林里住着一群兔子.有一天,兔子们突然决定要去看樱花.兔子们所在森林里的樱花树很特殊.樱花树由n个树枝分叉点组成,编号从0到n-1,这n个分叉点由n-1个树枝连接,我们可以把它 ...
- BZOJ 2021 [Usaco2010 Jan]Cheese Towers:dp + 贪心
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2021 题意: John要建一个奶酪塔,高度最大为m. 他有n种奶酪.第i种高度为h[i]( ...
- 洛谷P2507 [SCOI2008]配对 题解(dp+贪心)
洛谷P2507 [SCOI2008]配对 题解(dp+贪心) 标签:题解 阅读体验:https://zybuluo.com/Junlier/note/1299251 链接题目地址:洛谷P2507 [S ...
- 【BZOJ-1046】上升序列 DP + 贪心
1046: [HAOI2007]上升序列 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 3723 Solved: 1271[Submit][Stat ...
- Codeforces 675E Trains and Statistic(DP + 贪心 + 线段树)
题目大概说有n(<=10W)个车站,每个车站i卖到车站i+1...a[i]的票,p[i][j]表示从车站i到车站j所需买的最少车票数,求所有的p[i][j](i<j)的和. 好难,不会写. ...
- 【HDU 2546】饭卡(DP+贪心)
贪心:最贵的留到最后买.状态转移方程:dp[j]=dp[j+a[i]]|dp[j],dp[i]表示余下i元. 原来就不足5元,那就不能买啦. #include<cstdio> #inclu ...
- POJ 1065 Wooden Sticks / hdu 1257 最少拦截系统 DP 贪心
参考链接:http://blog.csdn.net/xiaohuan1991/article/details/6956629 (HDU 1257 解题思路一样就不继续讲解) POJ 1065题意:给你 ...
- HDU1069:Monkey and Banana(DP+贪心)
Problem Description A group of researchers are designing an experiment to test the IQ of a monkey. T ...
- 线段树+dp+贪心 Codeforces Round #353 (Div. 2) E
http://codeforces.com/contest/675/problem/E 题目大意:有n个车站,每个车站只能买一张票,这张票能从i+1到a[i].定义p[i][j]为从i到j所需要买的最 ...
随机推荐
- Tmux常用命令总结
会话 # 创建会话 tmux new -s work -s是session # 查看tmux进程 ps aux | grep tmux # 连接会话 tmux attach -t work # 会话分 ...
- 【Java面试】Mybatis中#{}和${}的区别是什么?
一个工作2年的粉丝,被问到一个Mybatis里面的基础问题. 他跑过来调戏我,说Mic老师,你要是能把这个问题回答到一定高度,请我和一个月奶茶. 这个问题是: "Mybatis里面#{}和$ ...
- vue虚拟dom和diff算法
vue的虚拟dom和diff算法 1.虚拟dom 虚拟dom,我的理解就是通过js对象的方式来具体化每一个节点,把dom树上面的每个节点都变为对象里的一个元素,元素的子元素变为子节点,节点上面的cla ...
- 【python基础】第09回 数据类型内置方法 01
本章内容概要 1.数据类型的内置方法简介 2.整型相关方法 3.浮点型相关方法 4.字符串相关方法 5.列表相关方法 本章内容详情 1.数据类型的内置方法简介 数据类型是用来记录事物状态的,而事物的状 ...
- Django快速入门之项目配置
开始 环境 python:3.6.2 django:2.0.5 跑起来 用pycharm导入或新建一个Django项目,在目录中存在manage.py的文件,通过下列指令运行Django后台. pyt ...
- leetcode二叉树题目总结
leetcode二叉树题目总结 题目链接:https://leetcode-cn.com/leetbook/detail/data-structure-binary-tree/ 前序遍历(NLR) p ...
- nginx 出现An error occurred错误
原因是我nginx中conf文件的配置里面 location中的 这一块内容是 #注释的那两行 所以报错出现这个错误. 后来将这两行注释掉,改成这两个就好了. root html; index ind ...
- get前言之版本控制-get和SVN的区别
版本控制什么是版本控制版本控制(Revision control)是一种在开发的过程中用于管理我们对文件.目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术. ~实 ...
- 洛谷P2709 小B的询问 莫队做法
题干 这个是用来学莫队的例题,洛谷详解 需要注意的一点,一定要分块!不然会慢很多(直接TLE) 其中分块只在排序的时候要用,并且是给问题右端点分块 再就是注意add与del函数里的操作,增加数量不提, ...
- day02 Java_变量
参考: 变量的练习: 声明一个变量,一次声明多个变量. 声明变量直接初始化,先声明变量,而后再给变量初始化. 声明整型变量g,声明另一个整型变量h并赋值为h+10,输出变量h的值. 声明整型变量i,在 ...