Piling Papers (USACO Gold 2023 February)
这个题初看还是比较复杂的,应该能看出是一个DP题,但是要数的是一个区间取值内的方案数,这个怎样构造状态是一个有难度的事情。测试点条件中间有一个的提示,所以我们可以从不是区间,而是确定的值情况开始进行思考。
如果我们限定只有一个固定的取值,那么DP状态应该就相对直观,比如我们确定从一个位置开始取纸(有了对应的所有结果后,倍复杂度就可以得到所有结果,以执行需要的查询),那么定义为:前张纸,正好匹配的方案数。因为每次处理一个数字时,三种选择分别是丢掉、放到现有数字末尾,或者前面。所以基于这个三动作可以写出的状态转换:
通过这个DP,可以把测试点4-5做出来。
接着考虑怎么把这个扩展到区间。我做此题的时候,想直接通过将区间转化成多个模式的方法来完成(比如对应三个模式,这个事实证明会TLE掉,所以需要另想办法。
正解还是比较巧妙的,关键一步是想到区间方案数,可以转换成这两个“小于”条件的方案数之差,而且这种“小于”的方案数,可以用类似等于的DP来求出来。
定义:前张纸,形成位数字,而且小于 (), 等于 (), 或者大于 ()的方案数。
DP状态转移还是可以完成,类似上面等于情况,但更复杂一些。分成三种情况,例如要求的方案数,是下面五个之和:
- 前张纸,生成比中第位开始的位更小的位数的方案数,。就是把新的第张纸直接丢弃。
- 前张纸,生成比中第位开始的位更小的位数,再跟上任意数(第张纸上的数)。这是一种追加一位的方式。
- 前张纸,生成和中第位开始的位相等的数,再跟上比中对应位置数字更小的一位数(第张纸上的数)。这是另一种追加一位的方式。
- 类似的,还有在前面插入的方式,第张纸比对应位小,再加上前张纸生成的任意位数。
- 最后一种前面插入的方式,是张纸和对应位相等,再加上前张纸生成的比中对应位置小的位数。
完整逻辑看代码。得到所有DP状态后,的方案数,就是是,其中是的位数。最后求和项是位数小于位的数的方案数,也是满足条件的。
此题思维难度较大,DP状态转移也比较细节,实现了一些DP求数字区间方案数的办法,值得记下这次个方法。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pi;
int n, Q;
ll A, B;
int a[305];
int dp[305][19][19][3]; // dp[i][j][k][c]: first i numbers, covering digits [j, j+k-1], c=0 (less than A[j,j+k), 1 equal, 2 larger
int ans[305][305]; // result starting from a[i], len j
const int M = 1e9 + 7;
int clog10(ll x) {
int res = 0;
for (; x; x /= 10, res++);
return res;
}
vector<int> solve(int s, ll x) { // solve for starting position s, less than or equal to x
vector<int> r(n-s+1, 0);
int d = clog10(x);
vector<int> X;
for (; x; x /= 10)
X.push_back(x % 10);
reverse(X.begin(), X.end()); // most significant first
for (int i = 0; i <= n-s; i++) { // length
for (int j = 0; j < d; j++) { // start position in X
dp[i][j][0][1] = dp[i][j+1][0][1] = 1;
for (int k = 1; j+k <= d && k <= i; k++) {
int p = a[s+i-1];
// < X[j,j+k-1]
dp[i][j][k][0] = dp[i-1][j][k][0]; // drop p=a[s+i-1]
dp[i][j][k][0] = (dp[i][j][k][0] + dp[i-1][j][k-1][0]) % M; // append
if (p < X[j+k-1]) dp[i][j][k][0] = (dp[i][j][k][0] + dp[i-1][j][k-1][1]) % M; // append
if (p == X[j]) dp[i][j][k][0] = (dp[i][j][k][0] + dp[i-1][j+1][k-1][0]) % M; // prepend
if (p < X[j]) dp[i][j][k][0] = ((ll)dp[i][j][k][0] + dp[i-1][j+1][k-1][0]
+ dp[i-1][j+1][k-1][1] + dp[i-1][j+1][k-1][2]) % M; // prepend
// =
dp[i][j][k][1] = dp[i-1][j][k][1]; // drop
if (p == X[j+k-1]) dp[i][j][k][1] = (dp[i][j][k][1] + dp[i-1][j][k-1][1]) % M; // append
if (p == X[j]) dp[i][j][k][1] = (dp[i][j][k][1] + dp[i-1][j+1][k-1][1]) % M; // prepend
// >
dp[i][j][k][2] = dp[i-1][j][k][2]; // drop
dp[i][j][k][2] = (dp[i][j][k][2] + dp[i-1][j][k-1][2]) % M; // append
if (p > X[j+k-1]) dp[i][j][k][2] = (dp[i][j][k][2] + dp[i-1][j][k-1][1]) % M; // append
if (p == X[j]) dp[i][j][k][2] = (dp[i][j][k][2] + dp[i-1][j+1][k-1][2]) % M; // prepend
if (p > X[j]) dp[i][j][k][2] = ((ll)dp[i][j][k][2] + dp[i-1][j+1][k-1][0]
+ dp[i-1][j+1][k-1][1] + dp[i-1][j+1][k-1][2]) % M; // prepend
}
}
r[i] = ((ll)r[i] + dp[i][0][d][0] + dp[i][0][d][1]) % M;
for (int j = 1; j < d; j++)
for (int c = 0; c < 3; c++)
r[i] = (r[i] + dp[i][j][d-j][c]) % M;
}
return r;
}
int main() {
scanf("%d %lld %lld", &n, &A, &B);
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
for (int s = 0; s < n; s++) { // start position
vector<int> ra = solve(s, A-1);
vector<int> rb = solve(s, B);
for (int i = 0; i <= n-s; i++)
ans[s][i] = (rb[i] + M - ra[i]) % M;
}
scanf("%d", &Q);
for (int q = 0; q < Q; q++) {
int i,j;
scanf("%d %d", &i, &j);
printf("%d\n", ans[i-1][j-i+1]);
}
return 0;
}