传送门
题面:
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
",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
",res);
}
}