题目链接:Click here~~
题意:
给出 n 个点的有向图,每个点分别有一个数字 p[i] ,获得的分数为所经过的点的最小公倍数,且不能出现最小公倍数不变的情况。
起始时主人公在点 1,问走到点 n 时分数为 k 的方案数。
解题思路:
一道需要简化状态的 DP。
虽然原本的有向图并没有保证无环,但是由于 不能出现最小公倍数不变的情况 这个条件,于是本题就可以 dp 了,因为能走的路径一定不会出现环。
令 dp[u][score] 表示 走到点 u 时 分数为 score 的方案数,初始 dp[u][k] = 1。
则有 dp[u][score] = sum{ dp[v][lcm(score,v)] }(<u,v> ∈ G && k%lcm==0)。
但 score 的范围太大导致状态数太多,不能解。其实不难发现其实只有 score 为 k 的因子时,才可能有解,即状态才合法。
故我们可以根据这一点来简化状态,实现方法就是用 map 映射一下,用 dp[u][ M[score] ] 来表示状态。
所以二维只需要开到因子个数的最大值即可,不过我忘记一个数的因子个数是什么级别的了,亲测后发现,100万 大概是 256,10万 大概是 128,。
逆推比较麻烦,所以可以用记忆化搜索写,很方便。
#include <map>
#include <vector>
#include <stdio.h>
#include <string.h>
using namespace std;
const int N = 2e3 + 5;
const int mod = 1e9 + 7;
typedef long long LL;
inline int gcd(int a,int b){
return b ? gcd(b,a%b) : a;
}
inline LL lcm(int a,int b){
return (LL)a / gcd(a,b) * b;
}
int n,k,p[N],dp[N][256];
vector<int> g[N];
map<int,int> M;
int dfs(int u,int score)
{
int id = M[score];
if(dp[u][id] != -1)
return dp[u][id];
int ret = 0;
for(int i=0;i<(int)g[u].size();i++){
int v = g[u][i];
LL Lcm = lcm(score,p[v]);
if(k % Lcm || Lcm == score && u != 0)
continue;
(ret += dfs(v,Lcm)) %= mod;
}
return dp[u][id] = ret;
}
int main()
{
int m;
while(~scanf("%d%d%d",&n,&m,&k))
{
for(int i=0;i<=n;i++)
g[i].clear();
while(m--){
int u,v;
scanf("%d%d",&u,&v);
g[u].push_back(v);
}
for(int i=1;i<=n;i++)
scanf("%d",&p[i]);
if(k % p[n])
puts("0");
else{
g[0].push_back(1);
M.clear();
int id = 0;
for(int i=1;i*i<=k;i++)
if(k%i == 0){
M[i] = id++;
M[k/i] = id++;
}
memset(dp,-1,sizeof(dp));
dp[n][ M[k] ] = 1;
printf("%d\n",dfs(0,1));
}
}
return 0;
}