2019牛客国庆one D.Modulo Nine dp

62 篇文章 0 订阅

题目链接: https://ac.nowcoder.com/acm/contest/1099/D

题意:

你要构造一个长为 n n n 的数字串 a 1 a 2 a 3 a 4 . . . . a n a_1a_2a_3a_4....a_n a1a2a3a4....an,使得其满足 m m m 个条件,每个条件为一个区间 [ l i , r i ] [l_i,r_i] [li,ri] 要求 a l i ∗ a l i + 1 ∗ . . . ∗ a r i a_{l_i}*a_{l_i+1}*...*a_{r_i} aliali+1...ari m o d mod mod 9 = 0 9=0 9=0 ,问你能构造出多少这样的串。

做法:

把题目剖析一下大概就是,在这些区间中的数字必须要至少有一个为 0 0 0 或者分解质因数后有两个 3 3 3 。那么我们就可以把数字 3 , 6 3,6 3,6 当做数字 1 1 1 ,数字 9 , 0 9,0 9,0 当做数字 2 2 2 。即要求每个区间内的和要至少为 2 2 2

队友用的是记忆化搜索,大概意思 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 是到达第 i i i 个数字时,数字和为 0 0 0 的第一个区间为 k k k ,和为 1 1 1 的第一个区间为 j j j 。听起来能懂的样子然而…我是不会敲,膜一下膜一下…

后来看了人家的代码学到了另一种稍微简单一点的做法。

我们先维护好每一个右端点最大限制的左界在哪里(即对于位置 p o s pos pos ,要求的最近的左区间位置在哪里),同样以上面的理论作为基础 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 代表的是,到第 i i i 个格子的时候,之前的第一个 1 1 1 出现的位置为 j j j ,第二个 1 1 1 出现的位置为 k ( k < = j ) k(k<=j) k(k<=j) 的方案数。

当然,我们需要一些前置条件限制,如果我们想要从 d p [ i − 1 ] [ j ] [ k ] dp[i-1][j][k] dp[i1][j][k] 转移来,那么这个 k k k 必要满足 i = i − 1 i=i-1 i=i1 时候的情况。 如果已经满足,那假设我们这一位放的是 3 , 6 3,6 36, 那么最近的就可以变成 d p [ i ] [ i ] [ j ] dp[i][i][j] dp[i][i][j] 即最近的有位置 i i i 一份,假设我们这一位放的是 0 , 9 0,9 09, 那么最近的就可以变成 d p [ i ] [ i ] [ i ] dp[i][i][i] dp[i][i][i] ,即两个最近的 1 1 1 都已经是我当前枚举的位置了。然后就转移就可以了。

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i = (int)a;i<=(int)b;i++)
using namespace std;
typedef long long ll;
const int N=55;
const ll mod=1e9+7;
ll dp[55][55][55],ans;
int n,m,L[55];
void add(ll &a,ll b){
    a=(a+b)%mod;
}
void init(){
    ans=0ll;
    memset(L,0,sizeof(L));
    memset(dp,0,sizeof(dp));
    dp[1][1][1]=2ll;dp[1][1][0]=2ll;
    dp[1][0][0]=6ll;
}
int main(){

    while(~scanf("%d%d",&n,&m)){
        init();
        rep(i,1,m){
            int x,y; scanf("%d%d",&x,&y);
            L[y]=max(L[y],x);
        }
        rep(i,2,n){
            rep(j,0,i){
                rep(k,0,j){
                    if(k<L[i-1]) continue;
                    add(dp[i][j][k],6ll*dp[i-1][j][k]);
                    add(dp[i][i][j],2ll*dp[i-1][j][k]);
                    add(dp[i][i][i],2ll*dp[i-1][j][k]);
                }
            }
        }
        rep(j,0,n){
            rep(k,0,j){
                if(k<L[n]) continue;
                add(ans,dp[n][j][k]);
            }
        }
        printf("%lld\n",ans);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值