题目链接: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; }