题目链接: 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} ali∗ali+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[i−1][j][k] 转移来,那么这个 k k k 必要满足 i = i − 1 i=i-1 i=i−1 时候的情况。 如果已经满足,那假设我们这一位放的是 3 , 6 3,6 3,6, 那么最近的就可以变成 d p [ i ] [ i ] [ j ] dp[i][i][j] dp[i][i][j] 即最近的有位置 i i i 一份,假设我们这一位放的是 0 , 9 0,9 0,9, 那么最近的就可以变成 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;
}