P3352 [ZJOI2016]线段树
(^ w ^)
题目描述
小Yuuka遇到了一个题目:有一个序列a_1,a_2,?,a_n,q次操作,每次把一个区间内的数改成区间内的最大值,问最后每个数是多少。小Yuuka很快地就使用了线段树解决了这个问题。
于是充满智慧的小Yuuka想,如果操作是随机的,即在这q次操作中每次等概率随机地选择一个区间l,r,然后将这个区间内的数改成区间内最大值(注意这样的区间共有(n(n+1))/2个),最后每个数的期望大小是多少呢?小Yuuka非常热爱随机,所以她给出的输入序列也是随机的(随机方式见数据规模和约定)。对于每个数,输出它的期望乘((n(n+1))/2)q再对109+7取模的值。
输入输出格式
输入格式:
第一行包含2个正整数n,q,表示序列里数的个数和操作的个数。接下来1行,包含n个非负整数a1,a2…an。N<=400,Q<=400
输出格式:
输出共1行,包含n个整数,表示每个数的答案
输入输出样例
输入样例#1:
5 5
1 5 2 3 4
输出样例#1:
3152671 3796875 3692207 3623487 3515626
题解
换而言之就是求每个点所有区间组合之后结果之和。
我们注意到这显然是一个dp题,所以要思考一个状态之和。
f
j
,
i
,
x
,
y
\ f_{j,i,x,y}
fj,i,x,y表示进行
i
\ i
i此操作后,区间
[
x
,
y
]
\ [x,y]
[x,y]内,所有元素
≤
j
\ \le j
≤j,
a
y
+
1
,
a
x
−
1
≥
j
\ a_{y+1},a_{x-1} \ge j
ay+1,ax−1≥j的方案数,
g
i
,
j
\ g _{i,j}
gi,j表示不横跨
i
\ i
i,
j
\ j
j的区间的方案数。
我们可得一下dp式。
f
s
,
i
,
x
,
y
=
f
s
,
i
−
1
,
x
,
y
⋅
g
x
,
y
+
∑
j
=
1
y
−
1
f
s
,
i
−
1
,
j
,
y
⋅
(
j
−
1
)
+
∑
k
=
x
+
1
n
f
s
,
i
−
1
,
x
,
k
⋅
(
n
−
k
)
g
x
,
y
=
x
(
x
−
1
)
2
+
(
y
−
x
+
1
)
(
y
−
x
+
2
)
2
+
(
n
−
y
)
(
n
−
y
+
1
)
2
f_{s,i,x,y}=f_{s,i-1,x,y} \cdot g_{x,y} + \sum_{j=1}^{y-1} f_{s,i-1,j,y} \cdot (j-1) +\sum_{k=x+1}^{n} f_{s,i-1,x,k} \cdot (n-k) \\ g_{x,y}= \frac{x(x-1)}{2} + \frac{(y-x+1)(y-x+2)}{2} + \frac{(n-y)(n-y+1)}{2}
fs,i,x,y=fs,i−1,x,y⋅gx,y+j=1∑y−1fs,i−1,j,y⋅(j−1)+k=x+1∑nfs,i−1,x,k⋅(n−k)gx,y=2x(x−1)+2(y−x+1)(y−x+2)+2(n−y)(n−y+1)
由此可以想到一个O(
n
4
\ n^{4}
n4)的算法:离散化之后枚举,这显然是不好过的。然而有人卡过了。所以我们要像一个更优的算法。
我们发现第一维是固定的,式子也几乎不和第一维相关,所以我们改用其他状态。
f i , x , y \ f_{i,x,y} fi,x,y表示所有的第一维方案数之和。
转移式与之前完全相同:
f i , x , y = f i − 1 , x , y ⋅ g x , y + ∑ j = 1 y − 1 f i − 1 , j , y ⋅ ( j − 1 ) + ∑ k = x + 1 n f i − 1 , x , k ⋅ ( n − k ) g x , y = x ( x − 1 ) 2 + ( y − x + 1 ) ( y − x + 2 ) 2 + ( n − y ) ( n − y + 1 ) 2 f_{i,x,y}=f_{i-1,x,y} \cdot g_{x,y} + \sum_{j=1}^{y-1} f_{i-1,j,y} \cdot (j-1) +\sum_{k=x+1}^{n} f_{i-1,x,k} \cdot (n-k) \\ g_{x,y}= \frac{x(x-1)}{2} + \frac{(y-x+1)(y-x+2)}{2} + \frac{(n-y)(n-y+1)}{2} fi,x,y=fi−1,x,y⋅gx,y+j=1∑y−1fi−1,j,y⋅(j−1)+k=x+1∑nfi−1,x,k⋅(n−k)gx,y=2x(x−1)+2(y−x+1)(y−x+2)+2(n−y)(n−y+1)
复杂度O( n 3 \ n^{3} n3).
AC代码
// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
int n,q,a[410];
long long f[2][410][410],sum1[2][410][410],sum2[2][410][410],g[410][410];
bool cmp(const int &x,const int &y)
{
return a[x]<a[y];
}
int main()
{
scanf("%d%d",&n,&q);
int i=1,r=0,j=0,k=0;
a[0]=2000000000;
a[n+1]=2000000000;
while(i<=n)
{
scanf("%d",&a[i]);
++i;
}
i=1;
while(i<=n)
{
r=0;
j=i;
while(j<=n)
{
g[i][j]=i*(i-1)/2+(n-j)*(n-j+1)/2+(j-i+1)*(j-i+2)/2;
r=max(r,a[j]);
if((i==1)&&(j==n)) f[0][i][j]=r;
else if((a[i-1]>r)&&(a[j+1]>r)) f[0][i][j]=(r-min(a[i-1],a[j+1])+mod)%mod;
++j;
}
++i;
}
int now=1,last=0;
i=1;
while(i<=q)
{
j=1;
while(j<=n)
{
k=n;
while(k>=j)
{
sum2[last][j][k]=(sum2[last][j][k+1]+f[last][j][k]*(n-k))%mod;
--k;
}
++j;
}
j=1;
while(j<=n)
{
k=j;
while(k<=n)
{
sum1[last][j][k]=(sum1[last][j-1][k]+f[last][j][k]*(j-1))%mod;
f[now][j][k]=(f[last][j][k]*g[j][k]+sum1[last][j-1][k]+sum2[last][j][k+1])%mod;
++k;
}
++j;
}
now^=1;
last^=1;
++i;
}
long long ss=0;
i=1;
while(i<=n)
{
ss=0;
j=1;
while(j<=i)
{
k=i;
while(k<=n)
{
ss+=f[q&1][j][k];
ss%=mod;
++k;
}
++j;
}
printf("%lld ",ss);
++i;
}
return 0;
}
/*
1996年:东方灵异传(TOH1)
1997年:东方封魔录(TOH2)
1997年:东方梦时空(TOH3)
1998年:东方幻想乡(TOH4)
1998年:东方怪绮谈(TOH5)
2002年:东方红魔乡(TOH6)
2003年:东方妖妖梦(TOH7)
2004年:东方萃梦想(TOH7.5)
2004年:东方永夜抄(TOH8)
2005年:东方花映冢(TOH9)
2005年:东方文花帖(TOH9.5)
2007年:东方风神录(TOH10)
2008年:东方绯想天(TOH10.5)
2008年:东方地灵殿(TOH11)
2009年:东方星莲船(TOH12)
2009年:东方非想天则(TOH12.3)
2010年:东方文花帖DS(TOH12.5)
2010年:东方三月精(TOH12.8)
2011年:东方神灵庙(TOH13)
2013年:东方心绮楼(TOH13.5)
2013年:东方辉针城(TOH14)
2014年:弹幕天邪鬼(TOH14.3)
2014年:东方深秘录(TOH14.5)
2015年:东方绀珠传(TOH15)
2017年:东方凭依华(TOH15.5)
2017年:东方天空璋(TOH16)
*/