Problem
Hint
Solution
算法I:dfs+DP
考虑一下暴力怎么做。 我们可以
O ( k m )
O
(
k
m
)
暴力dfs出后面填的m个数。 此时,问题就转化成了快速计算一个序列本质不同的子序列个数。
考虑DP。 设f[i][j]表示前i个数,以j这个数值结尾的方案数。 假设第i位为x,则对于
∀ j ≠ x , f [ i ] [ j ] = f [ i − 1 ] [ j ]
∀
j
≠
x
,
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
;而
f [ i ] [ x ] = 1 + ∑ k j = 1 f [ i − 1 ] [ j ]
f
[
i
]
[
x
]
=
1
+
∑
j
=
1
k
f
[
i
−
1
]
[
j
]
。因为
∑ k j = 1 f [ i − 1 ] [ 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 ( n k + k m + 1 m )
O
(
n
k
+
k
m
+
1
m
)
的算法。期望得分:35points。 实际上,我们DP时不必
O ( n k )
O
(
n
k
)
。 首先,我们可以省去第一维,这样空间复杂度降为
O ( k )
O
(
k
)
;然后,我们DP的时候只会改变一个位置的值,其余位置都是沿袭i-1的,所以记录一个sum,每次转移可以只修改那个位置,于是做到时间复杂度
O ( n )
O
(
n
)
。 这样一来,时间复杂度便降为
O ( n + k m m )
O
(
n
+
k
m
m
)
。期望得分:49points。
算法II:贪心
观察到每次转移时,我们都是将某个位置变为1+sum。 那么我们有一种贪心的方法:那就是让其增量尽量大。换句话说,每次将最小的位置修改成1+sum。这样做sum的增量最大,故此贪心正确。 因此,我们每次都填当前dp值最小的那个元素,其实也就是最后出现位置最靠前的那个元素 。 随便用个堆或者set维护一下出现位置即可。
时间复杂度:
O ( n + m l o g 2 k + k )
O
(
n
+
m
l
o
g
2
k
+
k
)
。 期望得分:78points。
算法III:矩阵乘法优化
不难发现,我们填的一定是k的某个排列重复若干次。 因为我们每当填完一遍1~k后,比如填的是2,1,3;我们又会填最后出现位置最靠前的那个元素 ,就又会填2,填1,填3。 结合m的范围鬼畜的大,可以确定是矩乘优化。
首先,我们可以暴力
O ( k 3 )
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个矩阵自己乘自己,乘上
⌊ m k ⌋
⌊
m
k
⌋
次;然后再乘一下第m mod k个矩阵。 至此,我们得到了操作m次后的答案系数。那么直接乘上相应的dp值即可。
时间复杂度:
O ( n + k 3 l o g 2 m )
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=1l l*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);
}