Topcoder 10748 - StringDecryption(dp)

题面传送门

神仙题。

首先这个两次加密略微有点棘手,咱们不妨先从一次加密的情况入手考虑这个问题。显然,一次加密等价于将加密过的序列划分成若干段,每一段都是 \(xd\) 的形式表示这一段中有 \(x\) 个字符 \(d\)。那么我们就可以设 \(dp_{i}\) 表示原字符串长度为 \(i\) 的前缀可以由多少个字符串经过一次加密得到,转移就枚举上一段结尾 \(j(j\le i-2)\) 然后转移即可,只不过 \(j\) 可以转移到 \(i\) 需要满足两个条件:一是上一段的结尾与这一段的结尾不同,即 \(s_i\ne s_j\),二是这一段不能出现前导零,即 \(s_{j+1}\ne 0\)。

接下来考虑两次加密的情况,我们还是按照一次加密的情况枚举上一段的结尾 \(j\),这样第一次解密出来就是 \(x\) 个字符 \(d\),其中 \(x\) 是 \(s[j+1...i-1]\) 连接而成的 \(i-j-1\) 位数,\(d\) 是 \(s_j\) 表示的数。以 \(x=6,d=5\) 为例,第二次解密共有以下划分方法:

  • 直接跳过这 \(x\) 位数,或者说,这次解密出来的 \(6\) 个 \(5\) 完全被划分在同一段中,并且最后一个 \(5\) 不是这一段的结尾,比方说前面有 \(3\) 个 \(3\),后面有 \(2\) 个 \(7\),那么第二次解密出来的结果如下:\(3335555552\) 个 \(7\)
  • 上一段没有剩余的字符留下来,并且这段中间被断开,那么由于划分出来相邻两段的最后一个字符不能相同,故这 \(x\) 个 \(d\) 最多被切一刀(否则假设这两个断点分别为 \(i,j\),那么显然 \(s_i,s_j\) 为这两段的结尾,而由于 \(s_i=s_j\),不符合要求)。还是以 \(x=6,d=5\),前面有 \(3\) 个 \(3\),后面有 \(2\) 个 \(7\) 的情况为例,有以下 \(5\) 种划分方法:
    • \(333|55|555577\)
    • \(333|555|55577\)
    • \(333|5555|5577\)
    • \(333|55555|577\)
    • \(333|555555|77\)
  • 上一段有剩余的字符留下来,并且这段中间被断开,照样采用上面的例子,不妨设前面三个 \(3\) 在第二、三个 \(3\) 中间切了一道,那么有以下 \(6\) 种划分方式:
    • \(33|35|5555577\)
    • \(33|355|555577\)
    • \(33|3555|55577\)
    • \(33|35555|5577\)
    • \(33|355555|577\)
    • \(33|3555555|77\)

受到这个思想的启发,我们可以设 \(dp_{i,d,k}\) 表示当前解密了原字符串的前 \(i\) 位,在第一次解密出来的字符串中进行划分,划分出来最后一段的最后一位为 \(d\),当前第一次解密出来的字符串中是否有字符还没有划分为完整的一段的情况为 \(k\) 的方案数。转移还是枚举原字符串中上一段的右端点位置为 \(j\),上一段最后一个字符 \(p\),我们假设 \(s[j+1...i-1]\) 组成的数为 \(x\),\(s_i=d\),那么可以分以下情况:

  • 第一次解密出来的 \(x\) 个 \(d\) 中间没有划分,那么显然这 \(x\) 个 \(d\) 还没有被划分为完整的一段,故 \(dp_{i,p,1}\leftarrow dp_{j,p,1},dp_{i,p,1}\leftarrow dp_{j,p,0}\),当然如果 \(d=0\) 就不能从 \(dp_{j,p,0}\) 转移,因为这样会出现 \(pppp|000...0\) 的情况,就会出现前导零了。
  • 上一段没有剩余的字符留下来,并且这段中间被断开,那么共有 \(x-1\) 种可能,其中 \(x-2\) 种有字符剩余,\(1\) 种没有字符剩余,故 \(dp_{i,d,1}\leftarrow dp_{j,p,0}·(x-2),dp_{i,d,0}\leftarrow dp_{j,p,0}\),当然如果 \(d=0\) 或 \(d=p\) 就无法转移了,因为会出现前导零或者相邻两段结尾位置相同的情况,\(x=1\) 时无法转移到 \(dp_{i,d,1}\)。
  • 上一段有剩余的字符留下来,并且这段中间被断开,那么共有 \(x\) 种可能,其中 \(x-1\) 种有字符剩余,\(1\) 种没有字符剩余,故 \(dp_{i,d,1}\leftarrow dp_{j,p,1}·(x-1),dp_{i,d,0}\leftarrow dp_{j,p,1}\),同理如果 \(d=0\) 或 \(x=1\) 也无法转移到 \(dp_{i,d,1}\),因为划分出来下一段的第一个字符为 \(0\),不合法。

最终答案即为 \(dp_{n,s_n,0}\)。

时间复杂度 \(10n^2\)

const int MAXN=500;
const int MOD=1e9+9;
int n,dp[MAXN+5][11][2],pw10[MAXN+5];
struct StringDecryption{
	int decrypt(vector<string> code){
		string s;
		for(int i=0;i<code.size();i++) s=s+code[i];
		n=s.size();s=" "+s;dp[0][10][0]=pw10[0]=1;
		for(int i=1;i<=n;i++) pw10[i]=10ll*pw10[i-1]%MOD;
		for(int i=1;i<=n;i++){
			int sum=0,dig=s[i]-'0';
			for(int j=i-2;~j;j--){
				sum=(sum+1ll*pw10[i-2-j]*(s[j+1]-'0'))%MOD;
				if(s[j+1]=='0'||s[j]==s[i]) continue;
//				printf("%d %d %d\n",i,j,sum);
				for(int k=0;k<=10;k++){
					if(dig!=0) dp[i][k][1]=(dp[i][k][1]+dp[j][k][0])%MOD;
					dp[i][k][1]=(dp[i][k][1]+dp[j][k][1])%MOD;
					if(dig!=k){
						if(dig!=0){
							if(!(j==i-2&&sum==1)) dp[i][dig][1]=(dp[i][dig][1]+1ll*(sum-2+MOD)*dp[j][k][0])%MOD;
							dp[i][dig][1]=(dp[i][dig][1]+1ll*(sum-1+MOD)*dp[j][k][1])%MOD;
						}
						dp[i][dig][0]=(dp[i][dig][0]+dp[j][k][1])%MOD;
						if(dig!=0&&!(j==i-2&&sum==1)) dp[i][dig][0]=(dp[i][dig][0]+dp[j][k][0])%MOD;
					}
				}
			}
//			printf("%d %d\n",dp[i][dig][0],dp[i][dig][1]);
		} return dp[n][s[n]-'0'][0];
	}
};
上一篇:冒泡排序1


下一篇:《痞子衡嵌入式半月刊》 第 33 期