HDU 6078 Wavel Sequence(区间动归 17多校第四场)

  • 题目大意

    定义波浪序列为满足 a1<a2>a3<a4... 的序列,现在给出两个数组a(长为n)和b(长为m),从a中选出满足波浪序列一个子序列f,b中选出满足波浪序列的子序列g,求有多少中选法满足f=g。

    数组长度 n,m2000 a[i]b[i]2000

  • 分析

    题目很像最长公共子序列,从动态规划的角度去思考.

    定义:

    dp[i][j][t] 表示a数组以i结尾,b数组以j结尾的方案数(t为0表示结尾是波谷,1表示波峰)

    最暴力的转移方程:

    dp[i][j][t]=0i1x=1j1y=1dp[x][y][(t+1)%2],(a[x]=b[y])(a[x]<a[i]t=0a[x]>a[i]t=1)a[i]b[j]a[i]=a[j]

    这样复杂度是 O(n4) ,根据数据这道题复杂度不会超过 O(n3) ,所以应该想 O(n2) O(n2logn) 的复杂度的算法.
    由于状态的个数就是 n2 了,我们尝试对状态的转移进行优化。
    一个优化的思想就是信息的重用,在当前的状态转移过程中用到的信息保留下来以便在之后的状态转移中使用。
    对于 a[i]=a[j] 的情况,在求 dp[i][j][t] 的时候我们希望知道的是i前面和j前面有多少可行方案数,正如前面所说,我们应该对状态的转移进行优化,优化状态的转移也就是维护“方案数”这个信息。
    在状态转移的两层循环中,外层的是i,内层的是j,也就是说在j变化的过程中i是不变的.
    我们用一个数组 sum[i][j][t] 表示对于a数组中的前i个数,b数组中以j为结尾的方案数的个数(t为0表示末尾是波谷,1表示波峰)。

    sum[i][j][t] 表示 {dp[k][j][t]|1k<j}

    有了这个信息之后我们就可以在对b数组进行扫描(j从1到m)的时候将满足 a[i]=a[j] sum[i][j][t] 相加即可得到 dp[i][j][(t+1)%2]

  • 总结

    这道题在dp的过程中通过一个sum数组来优化状态的转移,对sum数组的求和得到新的状态的值,巧妙的是sum数组的更新也是通过对dp数组的一个求和。
    由于状态的转移是通过sum数组得到dp[i]不需要用到dp[i-k],所以在实现的过程中可以将i的这一维数组降掉(空间上的降不是时间上)。相当于背包问题的一维实现。

  • 代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
#include<stack>
using namespace std;
const long long int MOD=998244353;
const int MAXN=2005;
int T;
int n,m;
int a[MAXN];
int b[MAXN];
int dp[MAXN][2];
int sum[MAXN][2];
void Work()
{
      memset(dp,0,sizeof(dp));
      memset(sum,0,sizeof(sum));
      long long int ans=0;
      long long int cnt0,cnt1;//cnt表示波谷的个数,cnt1表示波峰的个数

      for(int i=1;i<=n;i++)
      {
           cnt0=0;cnt1=1;
           for(int j=1;j<=m;j++)
           {
                 dp[j][0]=dp[j][1]=0;
                 if(a[i]==b[j])
                 {
                      dp[j][0]=cnt1;
                      dp[j][1]=cnt0;
                      ans=(ans+cnt0+cnt1)%MOD;
                 }
                 else if(a[i]>b[j]) cnt0=(cnt0+sum[j][0])%MOD;
                 else cnt1=(cnt1+sum[j][1])%MOD;
           }
           for(int j=1;j<=m;j++)
           {
                 sum[j][0]=(sum[j][0]+dp[j][0])%MOD;
                 sum[j][1]=(sum[j][1]+dp[j][1])%MOD;
           }
      }

      cout<<ans<<endl;
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        for(int i=1;i<=m;i++)scanf("%d",&b[i]);
        Work();
    }
    return 0;
}
/*
1
3 5
1 5 3
4 1 1 5 3
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值