fjutacm 1593 翻倍序列 线性dp

Problem Description
满足序列中除了第一个数外每个数是它前一个数的倍数的序列被称为翻倍序列 现在要求满足如下条件的翻倍序列: 1.序列的所有元素是1~n的正整数 2.序列的长度是k 求满足这样的翻倍序列的数有几种。 由于答案能很大,输出答案MOD 1000000007(10^9+7)

Input
输入包括多组数据 每组数据是一行两个整数n,k。(1<=n,k<=2000)

Output
对于每个输入数据,输出一个整数表示答案

SampleInput
3 2
6 4
2 1

SampleOutput
5
39
2

题目其实很好理解,然后我们先用笨方法大概过一遍,符合条件的序列,第一个位置的数可以是1~n,也就是n种可能;第二位的数只要是第一位的倍数,那也就都是可以选择的。也就是说,特别是作为基数的第一位数字如果是1的话,一次性将带来巨多种可能,而这种情况又是不可避免的,所以我们明确两点:一是暴力不可取,二是一步一取模很有必要。

我这篇博客的思路其实是学长的:https://www.cnblogs.com/qq136155330/p/9102857.html。不过我自己消化了很久,重新整理了一下。

首先我们要明确一下大概方向。为什么要用dp?因为我们仔细思考一下不难知道,按照题目的规则,一个翻倍序列的可能性受其最后一个元素(之后简称尾数)的大小的约束,并且两种长度相同、尾数不同的翻倍序列所带来的可能性之间完全没有关系;但长度不同、较长者的尾数为较短者尾数的倍速的两种翻倍序列所带来的可能性,较长的可能性一定能通过较短的转移得到,只多不少。

比如,我们设已经得到了长度为1、尾数为2的翻倍序列所带来的可能性是dp[1][2],那么dp[2][4]、dp[2][6]、dp[3][2]、dp[3][4]都只会是dp[1][2]加上多出的位数所带来的新的可能性。

然后不多说虚的,结合代码讲具体思路。

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <algorithm>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <vector>
#include <math.h>
#include <iostream>
const int MAX=2e3+5;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int dp[MAX][MAX]; /// dp[l][i],l代表长度,i代表尾数

int main()
{
  int i, j, n, k, l;
  /// 因为我们知道,较长的翻倍序列和较短的通过尾数相关联,所以这里我们先做
  ///好预处理,把所有长度下的所有尾数情况带来的可能性都一一预先处理好。
  for (i=1; i<MAX; i++)
    dp[1][i] = 1;/// 长度为1的时候,不论尾数是几,整个序列都只有自己1种可能
  for (l=2; l<MAX; l++)/// 从长度为2的序列开始处理到完
    for (i=1; i<MAX; i++)
      for (j=i; j<MAX; j+=i)
        dp[l][j] = (dp[l][j]+dp[l-1][i])%1000000007;
/// 这里通过遍历上1长度的所有尾数情况带来的可能性,在当前长度中寻找尾数对应
///成倍数的位置,进行状态转移。一步一取模。
        
        
  int ans;
  while (cin>>n>>k)
  {
    ans = 0;
    for (i=1; i<=n; i++)
      ans = (ans+dp[k][i])%1000000007;
    /// 既然给定了长度k和尾数上限n,那把长度k下尾数为1~n的所有情况的可能都
    ///加起来就行了。一步一取模
    cout << ans << endl;
  }
  return 0;
}

发布了19 篇原创文章 · 获赞 0 · 访问量 496

猜你喜欢

转载自blog.csdn.net/qq_43317133/article/details/102001120
今日推荐