昨天晚上看了一下题目,觉得是DP,后来再看看觉得挺难的。。搞了搞没搞出来。。
于是看了一下老师给的题解,写得很简略。但大致有了个思路,第一次用三维的DP,觉得好神奇。
然后就想着我要写这道题的题解。
今天上午花了大半个上午,先后换了三种方法,终于做出来了。
========================================================
题目大意:原题用英文说得很拗口,翻译过来就是1-N N个数的 序列内进行比较,问有几种排列方式可以出现K个“<”号。
eg:1 3 5 4 2 can be changed to 1<3<5>4>2.
解题思路:
dp[i][k][j] 表示 当有 i 个数,“<” 有 k 个,且该排列方式以小于等于j 的数结尾时有几种排列方式。
初始化:dp[0][0][0] = 1;
状态转移方程:dp[i][k][1] = dp[i-1][k][i-1]
dp[i][k][j] = dp[i][k][j-1] + ( dp[i-1][k][i-1] - dp[i-1][k][j-1] + dp[i-1][k-1][j-1] )
注意点:
1. 题目数据很大,但题目要求将结果%2007输出,所以在每次算完 dp[i][k][j] 后都将 dp[i][k][j] % 2007 ;
2. 由1 会导致另一个问题,在 “ dp[i-1][k][i-1] - dp[i-1][k][j-1] ” 出现值负数的情况,每次均需将所得 dp[i][k][j] + 2007*x 直至 dp[i][k][j]为正数。
3. 将对整个三维数组的求解放在外面,则只需进行一次求解。
代码:
#include<iostream>
#include<memory.h>
#include<cstdio>
using namespace std;
int dp[105][105][105];
int main()
{
dp[0][0][0] = 1;
for(int i = 1 ; i <= 100 ; i++)
{
for(int k = 0; k < i ; k++)
{
dp[i][k][1] = dp[i-1][k][i-1];
for(int j = 2; j <= i ; j++)
{
dp[i][k][j] = dp[i][k][j-1] + dp[i-1][k][i-1] - dp[i-1][k][j-1];
if(k>0) dp[i][k][j] += dp[i-1][k-1][j-1];
if(dp[i][k][j] < 0) dp[i][k][j]+= 2007;
if(dp[i][k][j] >= 2007) dp[i][k][j] %= 2007;
}
}
}
int n,k;
while(scanf("%d%d" , &n , &k)!=EOF)
{
printf("%d\n", dp[n][k][n]);
}
}
==========================================================
心酸过程:
一开始定义dp的状态为 i 个数 ,k 个小于号,最后一位为j,搞了四个for,结果TLE了。
然后后来想到上面的定义状态的方法,结果WA了,原因是当时没想到将dp[i][k][j-1]加上去。
然后问了胖丁,胖丁提议搞一个sum数组来存。我觉得思路很正确,但是不知道为什么就是得不到正确的结果,可能是状态转移方程有误。
后来我突然想到我之前的方法可用。
最后是你看到的这个。