F. 地精部落
题目描述
输入格式
输出格式
样例
数据范围与提示
大佬们都说这道题是水题,然而我貌似搞了一天,其实开始就想出来了一个n3的算法,但是肯定会T,我以为是个组合数的题,就去想其他解法了,然而那个n3再改一下就是正解了……
题解:
设f[i][j][0]表示考虑前i段,前i个数,第i段为j且为山谷,f[i][j][1]第i段为j且为山峰。那么f[i][j][0]=∑f[i-1][k][1];(j<k<i)。
这样是n3怎么优化呢?可以发现f[i][j][0]与f[i][i-j+1][1]是一一对应的(相当于是把原来的山峰变成了山谷,山谷变成了山峰),不明白可以去手膜样例。所以f[i][j][0]=∑f[i-1][i-k+1][0](j+1<=k<i)=∑f[i-1][k][0](1<=k<=i-j);
其实后面的0就完全可以去掉了:f[i][j]=∑f[i-1][k](1<=k<=i-j),那么可以用一个变量辅助dp,就可以变为n2了。
1 for(int i=2;i<=n;i++) 2 { 3 tem=0; 4 for(int k=1;k<=i-1;k++) 5 tem=(tem+f[i-1][k])%p; 6 for(int j=1;j<=i;j++) 7 f[i][j]=tem,tem=((tem-f[i-1][i-j])%p+p)%p; 8 }
最后答案为(∑f[n][i])*2,因为最后一个点为山峰和山谷的方案数是一样的,乘2即可。
#include<iostream> #include<cstdio> #define LL long long using namespace std; LL n,p; LL f[4210][4210]; signed main() { scanf("%lld%lld",&n,&p); LL tem=0,ans=0; f[1][1]=1; for(int i=2;i<=n;i++) { tem=0; for(int k=1;k<=i-1;k++) tem=(tem+f[i-1][k])%p; for(int j=1;j<=i;j++) f[i][j]=tem,tem=((tem-f[i-1][i-j])%p+p)%p; } for(int i=1;i<=n;i++)ans=(ans+f[n][i])%p; ans=(ans+ans)%p; cout<<ans<<endl; }
但是这样在bzoj上A不了,要用滚动数组;
#include<iostream> #include<cstdio> #define LL long long using namespace std; LL n,p,tem=0,ans=0;; LL f[2][4210]; signed main() { scanf("%lld%lld",&n,&p); f[1][1]=1; for(int i=2;i<=n;i++) { tem=0; for(int k=1;k<=i-1;k++) tem=(tem+f[(i-1)%2][k])%p; for(int j=1;j<=i;j++) f[i%2][j]=tem,tem=((tem-f[(i-1)%2][i-j])%p+p)%p; for(int k=0;k<=n;k++) f[(i-1)%2][k]=0; } for(int i=1;i<=n;i++)ans=(ans+f[n%2][i])%p;ans=(ans+ans)%p; cout<<ans<<endl; }