ZOJ 3644 Kitty's Game(状态简化 & DP)

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


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值