首页 > 其他分享 >P1415 题解

P1415 题解

时间:2022-08-26 02:02:51浏览次数:114  
标签:return string int 题解 P1415 length num dp

前言

题目传送门!

更好的阅读体验?

这题是一道挺好的 \(\texttt{dp}\) 题啊,但大家的题解都写得不够详细。

所以,我来补一篇 \(\LaTeX\) 题解,希望能帮助大家。

思路

首先是读入,为了方便,我让字符串下标从 \(1\) 开始。

string a;
int n; //字符串长度。
void Input()
{
	cin >> a;
	//个人习惯将起点下标变为 1 来算。
	n = a.length();
	a = '@' + a;
}

为了方便,我们后面用 \(num(x, y)\) 表示下标为 \([x, y]\) 所构成的数。

容易想到,题目就是要我们求出:任意一个位置的最大结束下标

考虑到,算这个之前,我们还得先算一算:任意一个位置的最小开始下标

具体地,设 \(f_i\) 表示将前 \(i\) 位拆成递增数,最后一个数最小为 \(num(f_i, i)\)。

想要最小,则 \(f_i\) 要尽可能大

我们可以画一个图,方便推状态转移方程。

所以,状态转移方程如下。

\[f_i = \max\limits_{j=1}^{i}\begin{cases}j & num(f_{j-1}, j-1) < num(j, i)\\1 & j = 1\end{cases} \]

这里,我们发现一个问题:如何比较两个数的大小?

题解里的解决办法,代码稍长,提供一种简单的比较方式。

  1. 分离出 \(num(x, y)\),注意要去除前导 \(0\)。
  2. 比较两个数(注意两个数实际上使用 string 存储的):
    • 如果长度不等,则长度小的数小。
    • 如果长度相等,直接按字典序比较即可。

这样代码会短很多。

string num(int x, int y)
//将 a[i]~a[j] 的数分割出来,去除前导 0。
{
	string s = a.substr(x, y-x+1);
	while (s.length() > 1 && s[0] == '0') s.erase(0, 1);
	return s; 
}
bool Less(string x, string y)
//比较 x 与 y 两个数,注意不是比较字典序。
//当且仅当 x < y 返回 true。 
{
	if (x.length() != y.length()) return x.length() < y.length();
	return x < y; //长度相等,则字典序比较等同于数的比较。 
}
bool cmp(int x1, int y1, int x2, int y2)
//等同于:比较 num(x1, y1) 与 num(x2, y2) 的大小。
{
	string t1 = num(x1, y1), t2 = num(x2, y2);
	return Less(t1, t2);
}

有了这个 cmp() 函数,我们就可以很方便地完成第一次动规。

int f[N];
void DP1()
{
	for (int i = 1; i <= n; i++)
	{
		for (int j = i; j >= 2; j--) //从后往前枚举,第一次枚举到的 j 一定是最大的。
			if (cmp(f[j-1], j-1, j, i))
			{
				f[i] = j;
				break;
			}
		if (f[i] == 0) f[i] = 1; //如果没有符合的,则从头开始算一个。 
	}
}

接下来,就是处理『最小开始下标』了。

设 \(dp_i\) 表示将 \([i, n]\) 这一段拆成递增数,第一个数最大为 \(num(i, f_i)\)。

我们再画一张图。

所以,状态转移方程如下。

\[dp_i = \max\limits_{j = i}^{f_n - 1}\begin{cases}j & num(i, j) < num(j+1, dp_{j+1})\end{cases} \]

并且有 \(dp_{f[n]} = n\)。

但是,这里并没有那么简单!

如果你直接就这样打了,可能只会获得 \(\texttt{90pts}\)。

错误原因是后面的 \(0\) 造成的。

举例来说,有一个数 \(\texttt{1234050}\)。

如果单纯这样做,结果为 \(\texttt{1,2,3,40,50}\)。

发现 \(f_7 = 6\),所以初始化 \(dp_6 = 7\)。

但是,第 \(5\) 位是 \(\texttt{0}\),根据样例,\(\texttt{050}\) 也是符合的,并且末尾元素仍然最小!

显然,保持最优解的情况下,位数多一点更好。

所以,我们应该也让 \(dp_5 = 7\)。

这样,结果为 \(\texttt{12,34,050}\)。

代码比较好打,只需要注意此处的判断即可。

int dp[N];
void DP2()
{
	dp[f[n]] = n;
    //关键步骤,增添最后一个数的前导 0。
	int pos = f[n];
	for (pos = f[n]; pos-1 && a[pos-1] == '0'; pos--) dp[pos-1] = n;
	for (int i = pos-1; i; i--) //剩下的就和 DP1() 基本相等。
		for (int j = f[n] - 1; j >= i; j--)
			if (cmp(i, j, j+1, dp[j+1]))
			{
				dp[i] = j;
				break;
			}
}

大功告成,最后输出比较容易了。

void Output()
{
	string ans = ""; //本质就是输出了,只不过要处理末尾逗号罢了。 
	for (int i = 1; i <= n; i = dp[i] + 1)
	{
		for (int j = i; j <= dp[i]; j++) ans += a[j];
		ans += ',';
	}
	ans.erase(ans.length()-1, 1); //擦除最后一位逗号。
	cout << ans; 
}

代码

其实上面就是代码了,组合在一起即可。

但还是给出完整代码。具体注释参照上面。

这份完整代码保留的注释是调试语句,大家可以看一下。

码量不大。

#include <iostream>
#include <cstdio>
#define N 505
using namespace std;
string a;
int n;
void Input()
{
	cin >> a;
	n = a.length();
	a = '@' + a;
}
string num(int x, int y)
{
	string s = a.substr(x, y-x+1);
	while (s.length() > 1 && s[0] == '0') s.erase(0, 1);
	return s; 
}
bool Less(string x, string y)
{
	if (x.length() != y.length()) return x.length() < y.length();
	return x < y;
}
bool cmp(int x1, int y1, int x2, int y2)
{
	string t1 = num(x1, y1), t2 = num(x2, y2);
	return Less(t1, t2);
}
int f[N];
void DP1()
{
	for (int i = 1; i <= n; i++)
	{
		for (int j = i; j >= 2; j--)
			if (cmp(f[j-1], j-1, j, i))
			{
				f[i] = j;
				break;
			}
		if (f[i] == 0) f[i] = 1;
		//printf("dp[%d] = %d.\n", i, dp1[i]);
	}
	/*
	for (int i = 1; i <= n; i++) printf("f[%d] = %d.\n", i, f[i]);
	printf("\n");  */
}
int dp[N];
void DP2()
{
	dp[f[n]] = n;
	int pos = f[n];
	for (pos = f[n]; pos-1 && a[pos-1] == '0'; pos--) dp[pos-1] = n;
	//printf("pos = %d.\n", pos);
	for (int i = pos-1; i; i--)
		for (int j = f[n] - 1; j >= i; j--)
			if (cmp(i, j, j+1, dp[j+1]))
			{
				dp[i] = j;
				//printf("dp[%d] = %d.\n", i, dp2[i]);
				break;
			}
	//for (int i = 1; i <= n; i++) printf("dp[%d] = %d.\n", i, dp[i]);
}
void Output()
{
	string ans = "";
	for (int i = 1; i <= n; i = dp[i] + 1)
	{
		for (int j = i; j <= dp[i]; j++) ans += a[j];
		ans += ',';
	}
	ans.erase(ans.length()-1, 1);
	cout << ans; 
}
int main()
{
    Input();
    DP1();
    DP2();
    Output();
    return 0;
}

尾声

建议大家好好复盘一下状态转移方程。

顺便给出时间复杂度:

  • cmp() 函数的时间复杂度大约是 \(O(n)\),实际运行时可以理解为常数。
  • 两次 \(\texttt{DP}\) 的时间复杂度都是 \(O(n^2 \times n = n^3)\),实际运行接近 \(O(n^2)\)。

码字不易,感谢大家观看,希望能帮助到大家!

首发:2022-06-20 18:12:59

标签:return,string,int,题解,P1415,length,num,dp
From: https://www.cnblogs.com/liangbowen/p/16622887.html

相关文章

  • CF371B 题解
    前言题目传送门!更好的阅读体验?这题显然没有蓝的难度。其他题解代码不好看,而且没有讲清楚,那我补一发吧。题目简述有两个数\(a\),\(b\),每次操作可以给\(a\)或\(b\)......
  • P8410 题解
    前言题目传送门!更好的阅读体验?本次比赛第二题,好像没有人抢题解,那我来一发。思路还是挺巧妙的。\(\texttt{10pts}\)思路深搜求解即可。最坏情况,时间复杂度\(O(n!......
  • CF722B 题解
    前言题目传送门!更好的阅读体验?这是一道简单的字符串练手题。思路每次暴力计数,是否为元音。最后判断是否满足题意即可。重点是字符串读入问题。由于字符串读入部分......
  • AT1330 题解
    前言题目传送门!更好的阅读体验?这一题内部比赛时考到了,个人觉得是一道二分答案好题。本题时间很宽松,导致\(O(n\log^2n)\)的代码可以跑过去。但是,我内部比赛的时限......
  • P2130 题解
    前言题目传送门!更好的阅读体验?本题是练习bfs的好题。思路结合代码进行思路讲解。首先是读入部分,我们可以用bool存下地图,节省空间开销。需要注意,数据比较烂,起始......
  • CF1635B 题解
    题目传送门!更好的阅读体验?这题显然可以使用贪心的思想解决。由于头和尾一定不用更改,所以只需从\(a_2\)枚举到\(a_{n-1}\)。贪心原则下,我们更改的数应该要与相邻的......
  • P8295 题解
    ###前言题目传送门\(\color{red}{see}\space\color{green}{in}\space\color{blue}{my}\space\color{purple}{blog}\)这题并不困难,代码也挺短的,题目理解稍有困难。......
  • 题解 UVA10869 Brownie Points II
    题意平面上有若干点,\(\text{stan}\)通过一个点划了一条直线,\(\text{ollie}\)通过在这条直线上的点作了一条垂线,那么平面被分成\(4\)个象限,\(\text{stan}\)获得的分数......
  • idea导入依赖maven的dependenci列表报红问题解决
    打开一个idea的pom文件时,明明仓库有相关依赖,并且maven的仓库配置没有错误,但是maven的dependencies列表却报红,我们可以让idea每次加载pom文件的依赖不从idea的缓存中读取,而......
  • npm 报错:npm ERR! Maximum call stack size exceeded 超过最大栈问题解决方案
       错误的原因,npm版本问题;解决办法:   1、更新到最新版本:npminstallnpm-g  要记住全局更新2、回退版本:[email protected] ......