D-Modulo Nine
很妙的类似区间dp, 我自己是想不到,本题解题思路来自学长的博客: 长沙橘子猫
题意
有一个长度为 n n n 的序列,你可以给每个位置填 0 ∼ 9 0\sim9 0∼9 的一个数,有 m m m 个限制,每个限制 [ l i , r i ] [l_{i}, r_{i}] [li,ri] 要求区间内的数相乘必须为 9 9 9 的倍数,问一共有多少种合法的填数方案。
思路
破题点:博主在定义自己的方程时意识到,区间是不连续的两个端点组成的,我们枚举前
i
i
i 个数则是一位位顺序来的,这样转移方程就不会很顺利。
于是我们可以尝试往将区间也能随着我们顺序遍历来解决的方向虑,于是就引申出解法中,以右端点编号将所有右端点相同的区间的左端点存入同一个桶的做法。 (实际上我们只需要存最大左端点即可)文章来源:https://www.toymoban.com/news/detail-413611.html
而我们每遍历一位数,枚举当前可能填入的数之后就可以着手考虑如何让右端点为 i i i 的所有区间合法考虑,因为我们找到只要区间内包含两个及以上的 3 3 3 就能保证合法( 0 / 9 0/9 0/9 本身就代表两个 3 3 3),于是就能引申出dp方程的状态 j , k j,k j,k 分别代表离 i i i 最近的两个 3 3 3 的位置, d p j k dp_{jk} dpjk,就能轻易根据当前 i i i 桶里存储的区间来判断 d p j k dp_{jk} dpjk 的方案合不合法。文章来源地址https://www.toymoban.com/news/detail-413611.html
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 60, mod = 1e9 + 7;
int n, m;
ll f[N][N]; //前i个数 当前已经填过的数的最后一个3在j, 倒数第二个在i
vector<int>g[N];
void add(ll &x, ll y){
x = (x + y + mod) % mod;
}
void solve(){
for(int i = 0; i <= n; i ++){
g[i].clear();
for(int j = 0; j <= n; j ++) f[i][j] = 0;
}
for(int i = 1; i <= m; i ++){
int l, r;
cin >> l >> r;
g[r].push_back(l); // 根据右端点存储左端点, 其实根据转移方程只需要记录最大的左端点即可,因为只要最大的左端点被满足,那么小一些的肯定也能被满足
}
f[0][0] = 1;
for(int i = 1; i <= n; i ++){
/* 计算所有可能结果 */
for(int j = i - 1; ~j; j --){
for(int k = j; ~k; k --){
if(f[k][j] != -1){
add(f[i][i], f[k][j] * 2); // 0 / 9
add(f[j][i], f[k][j] * 2); // 3 / 6
f[k][j] = f[k][j] * 6 % mod; // 非3的倍数
}
}
}
/* 根据所给区间剔除不合法的解 */
for(auto l : g[i]){ // 根据当前填数的点为右端点遍历所有的左端点, 那么对于所有区间l ~ i 中没有两个以上3的都视为不合法
for(int j = 0; j < l; j ++){
for(int k = j; k <= i; k ++){
f[j][k] = -1;
}
}
}
}
ll ans = 0;
for(int i = 0; i <= n; i ++){
for(int j = 0; j <= i; j ++) {
if(f[j][i] != -1) add(ans, f[j][i]);
}
}
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
while(cin >> n >> m){
solve();
}
return 0;
}
到了这里,关于2019湖南省大学生程序设计竞赛题解(D)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!