哎~这一场就做了三个题目,全队倒数第一,简直是太弱了。
A Kitty's Game (ZOJ 3644)
题目链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3644
题意: 给出一个有向图,每个节点有一个权值pi, 有一个人从1节点出发(其权值为1节点的权值),前往n号节点,每经过一个节点,他的权值就变成了他经过这个节点前的权值和这个节点权值的最小公倍数,如果他经过这个节点后权值不发生变化则他就不能经过这个节点,问从1节点出发到n节点且权值为k的路径有多少条。
思路: 不能重复经过一个节点表明这个图是一个有向无环图,所以可以用dp的方法来做。下面我来考虑状态,仔细思考一下就能发现,所有有效的状态都是k的约数,所以总的状态数就是k的约数个。
所以我们可以定义dp[i][j] 表示从i节点出发到n节点且权值为j的路径数。
(ps: 表示的应该是 i ~n 而不是1~i,因为如果表示的是i~n的话就得蛋疼的先对图进行拓扑排序,或者反过来来建图, 这个在图上dp中是一个很关键的点,一定要有这个概念,然后在做题是选择合适的定义。)
所以有如下的更新
dp[i][j] += dp[v][lcm(j,pi)] (v为i节点所指向的节点)
中间注意要判值不变的情况~~。
还要注意把所用的约数hash了。
code:
#include <cstdio>
#include <iostream>
#include <cstring>
#define clr(x,y) memset(x, y, sizeof x)
#include <cmath>
#include <vector>
#define mo 1000000007
using namespace std;
typedef long long LL;
const int maxn = 1010;
const int maxe = 20010;
int id[1010], val[1000010], ta;
int p[maxn];
int n, m, k;
LL dp[maxn][1010];
struct edge
{
int to, next;
} G[maxe];
int head[maxn], si;
void add(int s, int t)
{
G[si].to = t;
G[si].next = head[s];
head[s] = si++;
}
LL gcd(LL a, LL b)
{
if (b == 0) return a;
else return gcd(b, a % b);
}
LL lcm(LL a, LL b)
{
return (a * b) / gcd(a, b);
}
LL dfs(int u, int bi)
{
if (dp[u][bi] != -1) return dp[u][bi];
LL & ans = dp[u][bi];
ans = 0;
for (int i = head[u]; i != -1; i = G[i].next)
{
int v = G[i].to;
if (val[p[v]] == -1) continue;
int mid = lcm(p[v], id[bi]);
if (mid == id[bi]) continue;
ans = (ans + dfs(v, val[mid])) % mo;
}
return ans;
}
int main()
{
//freopen("input.txt", "r", stdin);
int st ,ed;
while(scanf("%d%d%d", &n, &m, &k) != EOF)
{
clr(val, -1); ta = 0;
clr(head, -1); si = 0;
clr(dp, -1);
for (int i = 1; i <= m; i++)
{
scanf("%d%d", &st, &ed);
add(st, ed);
}
for (int i = 1; i <= n; i++)
{
scanf("%d", &p[i]);
}
for (int i = 1; i * i <= k; i++)
{
if (k % i == 0)
{
id[ta] = i;
val[i] = ta++;
if (k / i != i)
{
id[ta] = k / i;
val[k / i] = ta++;
}
}
}
dp[n][val[k]] = 1;
if (val[p[1]] == -1) printf("0\n");
else
{
printf("%lld\n", dfs(1, val[p[1]]));
}
}
return 0;
}