题意:
给T组数据,每组数组给一个n和m。n代表有n根小棍,问用这些小棍去组成一个A-B=C的等式有多少种方法,结果取模m。
题解:
先把题目所给的数字的摆法需要的个数,用数组存起来。
注意这个题并没有一个上界来让我们进行保存dfs过程中的值,所以只能进行每组数据的时候,重新将dp重置为-1,不然答案并不对。
根据等式,我们可以直接转换成A=B+C的形式,那么我们我们只需要枚举每一位的B和C进行加法运算,如果需要进位就标记一下。
这样我们一共有8种状态,换成二进制的形式
000 代表三个都没枚举完毕
001 代表C已经是确定的了
010 代表B已经是确定的了
011 代表BC都已经是确定的
100 代表A是确定的了(但是这种状态必须保证BC已存在,所以在计算过程中并不会单独出现)
101 同上 同时C已确定,但是这种状态不存在
110 同上 同时B已确定,但是这种状态不存在
111 代表三个都枚举出来了
dfs的时候,需要同时枚举出当前位的B和C,所以有个二重循环,用当前位BC的和计算出A的当前位,并标记是否有进位,如果B已存在,那么B只能出现数字0,如果C已存在,那么也只能出现数字0。
A成功枚举出来仅当当前枚举不出现进位,并且sum(当前BCA枚举出来的消耗)+3=剩余值,A才可成功,因为+3是指减号-号跟等号=的三根小棍。
状态转移的话,dp[i][j][k] 第一维表示剩余可用的小棍数,第二维表示是否需要进位,第三维表示当前枚举ABC的枚举状态,即上述。
#pragma comment(linker, "/STACK:102400000,102400000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1e3;
int n;
int use[]={6,2,5,5,4,5,6,3,7,6};
ll m;
ll dp[N][2][1<<3];
//001 c存在 010 b存在 100 a存在
ll dfs(int left,int up,int state)
{
if (dp[left][up][state]!=-1)
return dp[left][up][state];
if (left<=0)
return (!left && !up && state==7);
ll cnt=0;
for (int i=0 ; i<10 ; ++i)
{
if (state&2 && i)
break;
for (int j=0 ; j<10 ; ++j)
{
int b=i,c=j,a=(i+j+up)%10;
if ((state&1 && c) || (state&2 && b))
break;
int sum=(state&1?0:use[c])+(state&2?0:use[b])+use[a];
if (sum>left)
continue;
bool visit[8]={0};
bool need=(i+j+up)>=10;
for (int k=0 ; k<8 ; ++k)
{
bool flag=0;
int t=0;
if (k&1 && c)
t|=1;
if (k&2 && b)
t|=2;
if (k&4 && a && !need && left==sum+3)
{
t|=4;
flag=1;
}
if (!visit[t])
{
if (flag)
cnt=(cnt+dfs(left-sum-3,need,state|t))%m;
else
cnt=(cnt+dfs(left-sum,need,state|t))%m;
visit[t]=1;
}
}
}
}
return dp[left][up][state]=cnt;
}
int main()
{
int T;
scanf("%d",&T);
for (int test=1 ; test<=T ; ++test)
{
scanf("%d%lld",&n,&m);
memset(dp,-1,sizeof(dp));
printf("Case #%d: %lld\n",test,dfs(n,0,0));
}
return 0;
}