【JZOJ5836】【省选&A组模拟2018.8.21】Sequence(DP+贪心+矩阵乘法优化)

Problem

这里写图片描述

Hint

这里写图片描述

Solution

算法I:dfs+DP
  • 考虑一下暴力怎么做。
  • 我们可以 O(km) O ( k m ) 暴力dfs出后面填的m个数。
  • 此时,问题就转化成了快速计算一个序列本质不同的子序列个数。

  • 考虑DP。
  • 设f[i][j]表示前i个数,以j这个数值结尾的方案数。
  • 假设第i位为x,则对于 jx,f[i][j]=f[i1][j] ∀ j ≠ x , f [ i ] [ j ] = f [ i − 1 ] [ j ] ;而 f[i][x]=1+kj=1f[i1][j] f [ i ] [ x ] = 1 + ∑ j = 1 k f [ i − 1 ] [ j ] 。因为 kj=1f[i1][j] ∑ j = 1 k f [ i − 1 ] [ j ] 表示的是前i-1位的本质不同的子序列个数,它们都可以在末位加一个x,这样形成的子序列定然是也本质不同的;前面有个“1+”是因为x也可以单独一个作为一个子序列。

  • 观察到f[i]一定由f[i-1]转移过来。我们可以预先DP出f[1~n],然后对于每一种dfs出来的方案,我们再DP出f[n+1~n+m]。这样,我们就得到了一个 O(nk+km+1m) O ( n k + k m + 1 m ) 的算法。期望得分:35points。
  • 实际上,我们DP时不必 O(nk) O ( n k )
  • 首先,我们可以省去第一维,这样空间复杂度降为 O(k) O ( k ) ;然后,我们DP的时候只会改变一个位置的值,其余位置都是沿袭i-1的,所以记录一个sum,每次转移可以只修改那个位置,于是做到时间复杂度 O(n) O ( n )
  • 这样一来,时间复杂度便降为 O(n+kmm) O ( n + k m m ) 。期望得分:49points。
算法II:贪心
  • 观察到每次转移时,我们都是将某个位置变为1+sum。
  • 那么我们有一种贪心的方法:那就是让其增量尽量大。换句话说,每次将最小的位置修改成1+sum。这样做sum的增量最大,故此贪心正确。
  • 因此,我们每次都填当前dp值最小的那个元素,其实也就是最后出现位置最靠前的那个元素
  • 随便用个堆或者set维护一下出现位置即可。

  • 时间复杂度: O(n+mlog2k+k) O ( n + m l o g 2 k + k )
  • 期望得分:78points。
算法III:矩阵乘法优化
  • 不难发现,我们填的一定是k的某个排列重复若干次。
  • 因为我们每当填完一遍1~k后,比如填的是2,1,3;我们又会填最后出现位置最靠前的那个元素,就又会填2,填1,填3。
  • 结合m的范围鬼畜的大,可以确定是矩乘优化。

  • 首先,我们可以暴力 O(k3) O ( k 3 ) 弄出k个k*k的矩阵,第i个矩阵代表填了i次数后的答案系数。
  • 换句话说,假设矩阵a的第i行为{2,3,1,2},则第i个答案(元素i的答案)为2+3*dp[1]+1*dp[2]+2*dp[3]。也就是说,a[i][j]表示的是dp[j]对i产生的贡献。
  • 那么怎么弄出k个矩阵呢?其实,第i个矩阵与第i-1个矩阵不同的只有一行,那么对于那一行,我们枚举一列,再枚举矩阵i-1的一行,类似矩乘的方法转移即可。

  • 然后,我们让第k个矩阵自己乘自己,乘上 mk ⌊ m k ⌋ 次;然后再乘一下第m mod k个矩阵。
  • 至此,我们得到了操作m次后的答案系数。那么直接乘上相应的dp值即可。

  • 时间复杂度: O(n+k3log2m) O ( n + k 3 l o g 2 m )
  • 期望得分:100points。

Code

#include <bits/stdc++.h>
#define P(x,y) x=(x+y)%mo
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;

const int N=1e6+1,K=101,mo=1e9+7;
int i,j,l,n,k,a[N],la[K];
ll m,sum,f[K][K][K],x,y,dp[K],s[K][K],c[K][K],tc[K];
struct ele
{
    int x,la;
    inline bool operator<(const ele a)const {return la<a.la;}
}e[K];

template<class T> void read(T&x)
{
    char ch=getchar(); x=0;
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48), ch=getchar();
}

void times(ll (*a)[K],ll (*b)[K])
{
    fo(i,0,k)
        fo(j,0,k) 
        {
            c[i][j]=0;
            fo(l,0,k) P(c[i][j],a[i][l]*b[l][j]);
        }
    memcpy(a,c,sizeof c);
}

int main()
{
    freopen("sequence.in","r",stdin);
    freopen("sequence.out","w",stdout); 
    read(n); read(m); read(k);
    fo(i,1,n) 
    {
        read(a[i]), e[a[i]].la=i;
        x=(1+sum)%mo; P(sum,x-dp[a[i]]+mo);
        dp[a[i]]=x;
    }
    fo(i,1,k) e[i].x=i;
    sort(e+1,e+k+1);

    fo(i,1,k) f[0][i][i]=1;
    fo(i,1,k)
    {
        memcpy(f[i],f[i-1],sizeof f[i]);
        x=e[i].x;
        fo(y,0,k)
        {
            f[i][x][y]=0;
            fo(j,0,k) P(f[i][x][y],f[i-1][j][y]);
        }
        P(f[i][x][0],1);
    }

    ll quo=1ll*m/k; 
    fo(i,0,k) s[i][i]=1;
    f[k][0][0]=1;
    for(;quo;quo>>=1)
    {
        if(quo&1) times(s,f[k]);
        times(f[k],f[k]);
    }
    f[m%k][0][0]=1;
    times(f[m%k],s);

    ll ans=0; dp[0]=1;
    fo(i,1,k) 
        fo(j,0,k)
            P(ans,f[m%k][i][j]*dp[j]); 
    printf("%lld",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值