HDU 6415(dp/記憶化搜尋)

Chen_Jr_發表於2018-08-21

傳送門

題面:

Nash Equilibrium is an important concept in game theory. 

Rikka and Yuta are playing a simple matrix game. At the beginning of the game, Rikka shows an n×mn×m integer matrix AA. And then Yuta needs to choose an integer in [1,n][1,n], Rikka needs to choose an integer in [1,m][1,m]. Let ii be Yuta's number and jj be Rikka's number, the final score of the game is Ai,jAi,j. 

In the remaining part of this statement, we use (i,j)(i,j) to denote the strategy of Yuta and Rikka. 

For example, when n=m=3n=m=3 and matrix AA is 

⎡⎣⎢111241131⎤⎦⎥[121143111]


If the strategy is (1,2)(1,2), the score will be 22; if the strategy is (2,2)(2,2), the score will be 44. 

A pure strategy Nash equilibrium of this game is a strategy (x,y)(x,y) which satisfies neither Rikka nor Yuta can make the score higher by changing his(her) strategy unilaterally. Formally, (x,y)(x,y) is a Nash equilibrium if and only if: 

{Ax,y≥Ai,y  ∀i∈[1,n]Ax,y≥Ax,j  ∀j∈[1,m]{Ax,y≥Ai,y  ∀i∈[1,n]Ax,y≥Ax,j  ∀j∈[1,m]



In the previous example, there are two pure strategy Nash equilibriums: (3,1)(3,1) and (2,2)(2,2). 

To make the game more interesting, Rikka wants to construct a matrix AA for this game which satisfies the following conditions: 
1. Each integer in [1,nm][1,nm] occurs exactly once in AA. 
2. The game has at most one pure strategy Nash equilibriums. 

Now, Rikka wants you to count the number of matrixes with size n×mn×m which satisfy the conditions.

Input

The first line contains a single integer t(1≤t≤20)t(1≤t≤20), the number of the testcases. 

The first line of each testcase contains three numbers n,mn,m and K(1≤n,m≤80,1≤K≤109)K(1≤n,m≤80,1≤K≤109). 

The input guarantees that there are at most 33 testcases with max(n,m)>50max(n,m)>50.

Output

For each testcase, output a single line with a single number: the answer modulo KK.

Sample Input

2
3 3 100
5 5 2333

Sample Output

64
1170

題目描述:

    讓你構造一個n*m的矩陣,使得構造出的矩陣有且只有一個點,使得他是在這一行這一列是最大的,且保證這個矩陣中的值在[1,n*m]且每個數不重複。

題目分析:

    因為題目中要求有且只有一個點在一行一列中最大,因此我們考慮從大到小放點,這樣就保證了整張圖中唯一的一個符合條件的點是最大的那個點n*m。

    我們可以發現,在(i,j)中放第k個點,如果要滿足題目要求使得只有一個點最大,此時我們第k+1點必然只能放在第k個點所覆蓋的第i行或第j列中。第k+2個點也只能放在[k,k+1]個點所覆蓋的行和列上。

    因此我們可以發現,對於每一個點,都可以具有三種不同的狀態:

    (1)覆蓋一行 

    (2)覆蓋一列

    (3)對答案無影響(放在行和列的交界處)

    因此我們可以考慮使用記憶化搜尋或者dp進行解決。

    我們設dp[i][j][k]為當前放了i個點,覆蓋了i行以及j列的方案數。

    倘若當前的點要覆蓋一行,則有(n-i)*j種不同的方案去放,因此此狀態轉移即為:dp[i+1][j+1][k]=dp[i][j][k]*(n-i)*j;

    倘若當前的點要覆蓋一列,則有(m-j)*i種不同的方案去放,因此此時的狀態轉移即為dp[i+1][j][k+1]=dp[i][j][k]*(m-j)*i;

    倘若當前點要在交界處,則有(j*k-i)種不同的方案去放,因此此時的狀態轉移即為:dp[i+1][i][k]=dp[i][j][k]*(j*k-i)。

    綜合上訴我們就可以用記憶化搜尋或者遞推dp用O(n^2*m^2)的複雜度求出。(相對而言dp會更快一些)

    ps:我們要注意的是取模運算在運算過程中實質上是相當的慢!!請看下圖:

    在這個的做法,我在dp轉移的過程中全都加了%運算,可以看到這份程式碼的效率並不算高。

    但是,倘若我們將%符號壓少,最後的取模符號,我們可以發現,時間將會變成:

    我們可以發現,整份程式碼的效率直接提升了不止一倍!!同理,這個現象在記憶化搜尋中反應得更加明顯。

(同一份程式碼,一份較多取模,TLE,但是當減少模數後,時間直接縮短了近2s之多)

    具體原因應該跟取模符號的性質有關,具體可以參考下文:

程式碼:

    dp:

#include <bits/stdc++.h>
#define maxn 85
typedef long long ll;
ll dp[maxn*maxn][maxn][maxn];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        ll mod;
        scanf("%d%d%lld",&n,&m,&mod);
        for(int i=1;i<=n*m;i++){
            for(int j=1;j<=n;j++){
                for(int k=1;k<=m;k++) dp[i][j][k]=0;
            }
        }
        dp[1][1][1]=m*n;
        for(int i=2;i<=n*m;i++){
            for(int j=1;j<=n;j++){
                for(int k=1;k<=m;k++){
                    if(j*k<i) continue;
                    dp[i][j][k]+=(j*k-i+1)*dp[i-1][j][k];
                    dp[i][j][k]+=(dp[i-1][j-1][k]*(n-j+1)*k);
                    dp[i][j][k]+=(dp[i-1][j][k-1]*(m-k+1)*j);
                    dp[i][j][k]%=mod;
                }
            }
        }
        printf("%lld\n",dp[n*m][n][m]);
    }
    return 0;
}

    記憶化搜尋:

    

#include <bits/stdc++.h>
#define maxn 85
using namespace std;
typedef long long ll;
ll n,m;
ll mod;
ll dp[maxn*maxn][maxn][maxn];
ll dfs(ll num,ll i,ll j){
    if(dp[num][i][j]!=-1) return dp[num][i][j];
    ll tmp=0;
    if(i<n) tmp=(tmp+(n-i)*j*dfs(num+1,i+1,j))%mod;
    if(j<m) tmp=(tmp+(m-j)*i*dfs(num+1,i,j+1))%mod;
    if(i*j>num) tmp=(tmp+(i*j-num)*dfs(num+1,i,j))%mod;
    return dp[num][i][j]=tmp;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%lld%lld%lld",&n,&m,&mod);
        for(int i=1;i<=n*m;i++){
            for(int j=1;j<=n;j++){
                for(int k=1;k<=m;k++) dp[i][j][k]=-1;
            }
        }
        dp[n*m][n][m]=1;
        ll res=n*m%mod*dfs(1,1,1)%mod;
        printf("%lld\n",res);
    }
}

 

相關文章