缩点DP
对于任何一个DP的过程,我们都可以把他看作是对DAG图遍历一次拓扑排序,针对于一条路径 x → … → y x \to \ldots \to y x→…→y,中间的状态转移都是状态继承,我们就直接可以把中间的点去掉,把这个路径压缩为 x → y x \to y x→y,压缩之后,不改变答案的结果,即中间的点对结果没有任何帮助,仅仅是一个传递状态的作用,我们就可以压掉这些点。在DP中叫做路径压缩,也叫缩点。
此方法针对于DP状态转移十分稀疏(大多数都是状态继承),并且节点数很多的点。
缩点的方法有很多,例如动态缩点、数论缩点等等。
数论缩点
此题就可以数论缩点,针对于一条最短路径,其某一个落脚点一定是一个石头的后9个点之内(包括石头正好10个),缩点之后,必须让这些点和路径依旧存在,才能保证问题的等价行。
根据数论的知识,我们选择的下界可以是72(1-10任意相邻两个都能到达的下界),2520(1-10的 l c m lcm lcm,即最小公倍数下界)。
#include <bits/stdc++.h>
using namespace std;
#define FR freopen("in.txt","r",stdin)
#define FW freopen("out1.txt","w",stdout)
typedef long long ll;
ll stones[105];
bool dstones[10000];
int main()
{
ll L;
cin >> L;
int S,T,M;
cin >> S >> T >> M;
if(S == T)
{
int cnt = 0;
while(M--)
{
int loc;
cin >> loc;
if(loc % S == 0) cnt++;
}
cout << cnt;
return 0;
}
for(int i = 1; i<=M; i++) cin >> stones[i];
sort(stones+1,stones + M + 1);
stones[0] = 0;
stones[M+1] = L;
int p = 0;
for(int i = 0; i<=M; i++)
{
p += min(72ll,stones[i+1] - stones[i]);
dstones[p] = true;
}
dstones[p] = false;
int dp[10000];
for(int i = 0; i<10000; i++) dp[i] = 1000;
dp[0] = 0;
for(int i = 0; i<=p; i++)
{
if(dstones[i]) dp[i]++;
for(int step = S; step<=T; step++)
{
dp[i + step] = min(dp[i + step],dp[i]);
}
}
int ans = 1000;
for(int i = p; i<=p+10; i++)
{
ans = min(ans,dp[i]);
}
cout << ans;
return 0;
}