題意:
給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;
}