Vijos 1002 过河
这是我写的在Vijos上的第一题。这道题在我刚学完DP的时候,就做过。当时年少轻狂,没有看数据的范围,直接暴力DP,结果TLE。。。。后来就没有再碰过。知道最近觉得快要省赛了,有必要把原来没有做出来的题做一做,于是有了这篇博客。
从那时学完的最简单的动规后,又学了一个名叫状压DP的算法,状压即状态压缩,把没有用的状态全部排除掉。BZOJ上就有一道状压DP的题(互不侵犯king)传送门!!而过河这道题就是一道状压DP。根据我现在的习惯,做题先看数据范围,然后判定算法。(1 <= L <= 10^9)这么大的数,根据经验应该用log级的算法,但是再看题目显然用不到任何log级的算法。而应该是DP。那么既然是DP又有这么大的数据范围,所以应该是状压DP。这样算法就判断出来了。那么该怎么压缩呢?这里要用到一个定理
Px+(P+1)y=Q
x,y是未知数,P是跳跃的距离,P+1也是跳跃的距离,对于任意Q,Q>=P*(P-1), x,y一定存在正整数解,换句话说当两个石子之间的距离大于等于T*(T-1)时,中间有相当大的距离是每个点都可以跳到的,因为没有石子,所以对答案没有贡献,可以取模(%90),这样就很好办了。。。于是我们就成功地把状态给压缩了。
注意:石子并没有按顺序给出要排序。
代码:
#include<iostream> #include<cstdio> #include<algorithm> #define inf 1<<30 using namespace std; int l,s,t,m,f[900000],pos[200],a[900000]; void slove(){ int ans=0; for(int i=1;i<=m;i++) if(pos[i]%s==0) ans++; printf("%d",ans); } int main() { scanf("%d%d%d%d",&l,&s,&t,&m); for(int i=1;i<=m;i++) scanf("%d",&pos[i]); sort(pos+1,pos+m+1); if(s==t){ slove(); return 0; } for(int i=1;i<=m;i++){ int temp=pos[i]-pos[i-1]; pos[i]=pos[i-1]+temp%90; } l=pos[m]+(l-pos[m])%90; for(int i=1;i<=m;i++) a[pos[i]]=1; for(int i=1;i<=l;i++) f[i]=inf; f[0]=0; for(int i=1;i<=l;i++) for(int j=s;j<=t;j++) if(i-j>=0) f[i]=min(f[i-j]+a[i],f[i]); printf("%d",f[l]); }