[YsOI2020]归零
这波是标程被踩,已经没脸见人了。万人血书 @skydogli 写一份题解。
标签:dp
题意简述
给定数字
{\rm{subtask\ 0}}\ (n\le6)
按题意模拟爆搜 / dp。
{\rm{subtask\ 1}}\ (n \le 15)
容易发现
同时,一个数位不可能
预处理出每个数位可能被哪些数字占据。然后状压
复杂度
{\rm{subtask\ 2}}\ (S_i\in[0,4])
不会产生进位,只需要找到有几个位置不是
{\rm{subtask\ 3}}\ (S_i=9)
发现【四舍五入】一个 ⑨ 之后,会使前面连续的
大概很容易想到
考虑用记忆化搜索实现。转移每次找一个后面的
复杂度
参考代码:
// O(n^2)
#include <bits/stdc++.h>
const int MX = 56;
const int MOD = 998244353;
int dp[MX][MX][2];
char str[233];
int dapai(int x ,int y ,int t){
if(~dp[x][y][t]) return dp[x][y][t];
int ret = (x == 0 && y == 0);
if(y && t) ret = (ret + 1LL * y * dapai(x ,y - 1 ,1)) % MOD;
if(x){
if(x != 1) ret = (ret + dapai(x - 1 ,y ,0)) % MOD;
ret = (ret + dapai(x - 1 ,y + 1 ,1)) % MOD;
}
return dp[x][y][t] = ret;
}
int main(){
memset(dp ,-1 ,sizeof dp);
init();
scanf("%s" ,str);
int n = strlen(str);
printf("%d\n" ,dapai(n ,0 ,0));
return 0;
}
{\rm{subtask\ 4}}\ (S_i\in[5,8])
出题人并不会,他出这个部分分只是觉得好像有什么做法。
验题人给出了一种做法:
有一种复杂度与划分数方案相关的暴力,设
每次枚举【四舍五入】哪个或者选择消去一个
本地跑出结果后打表即可。
{\rm{subtask\ 5}}\ (n \le 40)
出题人出这个部分分只为放过复杂度正确但常数大的。
{\rm{subtask\ 6}}\ (n \le 64)
三句话题解:
注意到一个事实:【四舍五入】一个位置之后可以把左右两部分分成子问题。
设
转移只需要枚举当前【四舍五入】哪一位
复杂度
剪枝方法:预处理每个区间最多和最少【四舍五入】次数。不满足的区间则不递归。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
const int MX = 64 + 3;
const int MOD = 998244353;
int dp[MX][MX][MX * 2][2] ,n;
int C[MX * 2][MX * 2];
char str[MX][MX];
int maxmv[MX][MX][MX] ,minmv[MX][MX][MX];
void init(){
for(int i = 0 ; i < MX * 2 ; ++i) C[i][0] = 1;
for(int i = 1 ; i < MX * 2 ; ++i)
for(int j = 1 ; j < MX * 2 ; ++j)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
int calc(int n ,int m){
return C[n + m][m];
}
int dapai(int l ,int r ,int c ,int jw){
if(c < 0) return 0;
if(l > r) return !c;
int ind = (!jw) * n + jw * l;
if(~dp[l][r][c][jw]) return dp[l][r][c][jw];
if(c == 0) return !maxmv[ind][l][r];
int cur = 0;
int book[MX] = {0};
int ok = 1;
for(int k = r ; k >= l && ok ; --k){
book[k] = ok && str[ind][k] >= 5;
ok = ok && str[ind][k] == 9;
}
for(int k = l ; k <= r ; ++k){
if(str[ind][k] == 0) continue;
if(book[k] || (k == r && str[ind][k] >= 5)){
cur = (cur + 1LL * dapai(l ,k - 1 ,c - 2 ,jw) * calc(c - 2 ,1)) % MOD;
continue;
}
int tind = ((str[ind][k] < 5) * n + (str[ind][k] >= 5) * (k + 1));
for(int s = minmv[ind][l][k - 1] ; s <= c - 1 ; ++s){
if(s > maxmv[ind][l][k - 1]
|| (c - 1 - s) > maxmv[tind][k + 1][r]
|| (c - 1 - s) < minmv[tind][k + 1][r]) continue;
cur = (cur + 1LL
* dapai(l ,k - 1 ,s ,jw)
* dapai(k + 1 ,r ,c - s - 1 ,str[ind][k] >= 5) % MOD
* calc(s ,c - s - 1)) % MOD;
}
}
return dp[l][r][c][jw] = cur;
}
int main(){
memset(dp ,-1 ,sizeof dp);
init();
std::cin >> str[0];
n = strlen(str[0]);
std::reverse(str[0] ,str[0] + n);
str[0][n] = '0' ,n = n + 1;
for(int i = 0 ; i < n ; ++i) str[0][i] -= '0';
memcpy(str[n] ,str[0] ,sizeof str[0]);
for(int i = 0 ; i < n ; ++i){ // Ä£Ä⽫´Ëλ½øÎ»
memcpy(str[i] ,str[n] ,sizeof str[i]);
str[i][i]++;
for(int j = i ; j < n ; ++j){
str[i][j + 1] += str[i][j] / 10;
str[i][j] %= 10;
}
}
for(int i = 0 ; i <= n ; ++i){
for(int l = 0 ; l < n ; ++l){
for(int r = l ; r < n ; ++r){
for(int s = r ; s >= l ; --s){
maxmv[i][l][r] += (str[i][s] != 0);
maxmv[i][l][r] += (str[i][s] >= 5);
}
int ret = 0;
for(int s = l ; s <= r ; ++s){
ret += str[i][s];
if(ret % 10) minmv[i][l][r]++;
if(ret >= 5) ret = 1;
else ret = 0;
}
if(ret % 10) minmv[i][l][r]++;
}
}
}
int Ans = 0;
for(int i = minmv[n][0][n - 1] ; i <= maxmv[n][0][n - 1] ; ++i){
int ret = dapai(0 ,n - 1 ,i ,0);
Ans = (Ans + ret) % MOD;
}
printf("%d\n" ,Ans);
return 0;
}