CTU 2019 Open Contest I.SixPack (WA39)

DOOOR

题意

给定一个2行n列的矩阵,其中填好了一些数字(0-9),定义2*3的子矩阵为一个“六包“,现要用0-9填满这个2n的矩阵,要求每个六包中的数字的和都是K,求方案总数。

思路

暴力感觉很难写,DP应该是比较好下手
不难发现K≤54,列中的和≤18,每隔两列,列上的和相等。

处理每列的方案数

每列的填法数可能都不一样,那就干脆具体化,即把一个≤18的数a拆分成两个数的加法式,每项都要≤9,要分两种情况讨论(隔板法)
1.a≤9,就是a+1
2.a≥9,因为隔板到两边之间的球的数量都要≤9,即把两边不能放的位置减掉,即a-2*(a-9) +1
每一列有三种情况,即填入了0,1,2个

DP

1.设计状态
开始想两维,即第i列填k,然后方应该是列不出来了,因为i-1和i-2受限于前面的填法,不能直接乘,再for一层讨论i-1列,又要管i-5列,初始化开始就烦死了
要大致表示一个六包,给出两列填法才具体,也有了递推的那种连接感,方程一拍脑门就出来了
dp[i][j][k] = num[i][k] * dp[i - 1][p][j] ;
注意超范围跳过
初始化也好想,就是前两列的填法

然鹅为什么会WA#39!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

//I
#include <bits/stdc++.h>
using namespace std;
#define ha 1000000007
const int maxn=3e5+4;
int n,k,num[maxn][19],a[maxn][3],K,nv;
int dp[maxn][19][19];
int main() {
  cin >> n >> K >> nv;
  if(K>54)K=54;
  int ai,aj,v;
  for (int i = 1; i <= nv; i++) {
    scanf("%d%d%d", &aj, &ai, &v);
    a[aj + 1][ai + 1] = v;
  }
  for (int i = 1; i <= n; i++) {
    if (a[i][1] && a[i][2]) {
      num[i][a[i][1] + a[i][2]] = 1;
      continue;
    } else if (!a[i][1] && !a[i][2]) {
      for (int j = 0; j <=min(18,K); j++) {
        if (j <= 9)
          num[i][j] = j + 1;
        else
          num[i][j] = j - 2 * (j - 9) + 1;
          //xxx|xxxxxx|xxx 中间才能划线
      }
    } else {
      for (int j = max(a[i][1], a[i][2]); j <=min(K,9 + max(a[i][1], a[i][2])); j++) {
        num[i][j] = 1;
      }
    }
  }
  //根据已经填了的数的个数分情况讨论
  for (int j = 0; j <= min(18,K); j++) 
    for (int k = 0; k<=18&&j+k<=K; k++) {
      dp[2][j][k] = num[1][j] * num[2][k];
    }
    long long  ans = 0;
    for (int i = 3; i <= n; i++) 
      for (int j = 0; j <= min(K,18); j++) 
        for (int k = 0; k <=min(K-j,18); k++) {
          int p = K - j - k;
        if(p<0||p>18)continue;
          dp[i][j][k] = num[i][k] * dp[i - 1][p][j] % ha;
          //前i列,第i列填k,i-1列填j的种数
          //因为已经有数填上去了,所以不好暴力,如果只用两维,由于i-1填的值依据前面i-4填的值,不好初始化,也不能表示一整个六包的情况
        }
        for(int j=0;j<=min(18,K);j++)
        for(int k=0;k<=K-j;k++)ans+=dp[n][k][j]%ha,ans%=ha;
        cout<<ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值