【牛客网】排列计数机
dp+线段树+数学
10pts:n<=20
无脑二进制暴力枚举就可以了。
20pts:n<=100
大力dp,设\(dp[i][j][k]\)表示考虑了前i个数,并且第i个数必须选,前i个数所构成子序列的最大值为j,子序列权值为k的方案数。
大力转移即可。
复杂度:\(O(n^4)\)
代码:
namespace p20 {
int dp[110][110][110],ans;
void work() {
for(register int i=1; i<=n; ++i) {
dp[i][A[i]][1]=1;
for(register int j=1; j<=A[i]-1; ++j) {
for(register int k=1; k<=i-1; ++k) {
for(register int o=2; o<=i; ++o) {
dp[i][A[i]][o]=(dp[i][A[i]][o]+dp[k][j][o-1])%MOD;
}
}
}
for(register int j=A[i]+1; j<=n; ++j) {
for(register int k=1; k<=i-1; ++k) {
for(register int o=1; o<=i; ++o) {
dp[i][j][o]=(dp[i][j][o]+dp[k][j][o])%MOD;
}
}
}
}
for(register int i=1; i<=n; ++i) {
for(register int j=1; j<=n; ++j) {
for(register int k=1; k<=i; ++k) {
ans=(0LL+ans+1LL*dp[i][j][k]*Quick_Pow(k,m)%MOD)%MOD;
}
}
}
cout<<ans;
}
}
40pts:n<=1000
考虑20pts的dp,你会发现第一维是完全没必要的。
设\(dp[i][j]\)表示最大值为i的序列,权值为j的方案数。
转移方程:(对于每一个i)
复杂度:\(O(n^3)\)(完全跑不满,再加上3s时限,随便过)
代码:
namespace p40 {
int dp[1010][1010],ans;
void work() {
for(register int i=1; i<=n; ++i) {
dp[A[i]][1]=1;
for(register int j=1; j<=A[i]-1; ++j) {
for(register int o=2; o<=i; ++o) {
dp[A[i]][o]=(dp[A[i]][o]+dp[j][o-1])%MOD;
}
}
for(register int j=A[i]+1; j<=n; ++j) {
for(register int o=1; o<=i; ++o) {
dp[j][o]=(dp[j][o]+dp[j][o])%MOD;
}
}
}
for(register int j=1; j<=n; ++j) {
for(register int k=1; k<=n; ++k) {
ans=(0LL+ans+1LL*dp[j][k]*Quick_Pow(k,m)%MOD)%MOD;
}
}
cout<<ans;
}
}
20pts:m=1
当m=1时,显然就变成了求所有子序列的权值和。
那么,我们算一个数\(A[i]\)在多少个子序列中有贡献。
考虑到若一个数\(A[i]\)有贡献,那么在[1,i-1]这一段中所选的数必须小于\(A[i]\),而在[i+1,n]这一段中不管怎么选,都没有影响。
设在[1,i-1]中有\(x\)个数小于\(A[i]\),那么\(A[i]\)的贡献就是\(2^{n-i+x}\)
树状数组和快速幂搞一下就可以了。
代码:
namespace m1 {
int BIT[MAXN],ans;
void Add(int i,int x) {
while(i<=n)BIT[i]++,i+=i&-i;
}
int Query(int i) {
int res=0;
while(i>=1)res+=BIT[i],i-=i&-i;
return res;
}
void work() {
for(int i=1; i<=n; i++) {
int x=Query(A[i]);
ans=(ans+1LL*Quick_Pow(2,x+n-i))%MOD;
Add(A[i],1);
}
cout<<ans;
}
}
100pts:一般情况
由于权值可能为n,而n最大是1e5,显然我们不能存权值。
考虑我们最终求得答案肯定是\(k_1\cdot 1^{m}+k_2 \cdot 2^{m}+k_3 \cdot3^{m}+\cdots+k_n+{n^m}\)
再观察发现m最大才20,于是\(dp[i][j]\)表示最大值为i,其中记录的值为\(k_1\cdot 1^{j}+k_2\cdot 2^{j}+k_3 \cdot 3^{j}+\cdots+k_n\cdot n^{j}\)
同时,我们还需要一棵权值线段树来维护。
struct node {
int L,R,val[22],lazy;
}
void Up(int p) {
for(register int i=0; i<=m; ++i)tree[p].val[i]=(0LL+tree[p<<1].val[i]+tree[p<<1|1].val[i])%MOD;
}
再看一下转移。
40分转移1:
对于这个转移,我们直接把\([A[i]+1,n]\)这个区间的值乘以2。
40分转移2:
对于每个\(j\in[0,m]\)我们先求出\([1,A[i]-1]\)的val和,记为\(S_j\)。
那么,\(S_j=k_1\cdot 1^{j}+k_2\cdot 2^{j}+\cdots +k_n\cdot n^{j}\),\(k_i\)为构成权值为i的序列的方案数。
于是就有,
对于\((x+1)^j\)我们可以二次项展开。
对于\((x+1)^j\),有:
那么,
不懂得话自己手推一下吧
然后再塞回线段树中就好了。
复杂度:\(O(nm^2+nmlog(n))\)(时限虽然3s,但还是很卡常)
代码:
namespace p100 {
int dp[MAXN][30],C[22][22];
void init() {
C[0][0]=1;
for(int i=1; i<=20; i++) {
C[i][0]=1;
for(int j=1; j<=i; j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
}
}
struct node {
int L,R,val[22],lazy;
} tree[MAXN<<2];
node res;
void Up(int p) {
for(register int i=0; i<=m; ++i)tree[p].val[i]=(0LL+tree[p<<1].val[i]+tree[p<<1|1].val[i])%MOD;
}
void build(int L,int R,int p) {
tree[p].L=L,tree[p].R=R;
tree[p].lazy=1;
if(L==R)return;
int mid=(L+R)>>1;
build(L,mid,p<<1);
build(mid+1,R,p<<1|1);
}
void Down(int p) {
if(tree[p].lazy==1)return;
for(int i=0; i<=m; i++) {
tree[p<<1].val[i]=1LL*tree[p<<1].val[i]*tree[p].lazy%MOD;
tree[p<<1|1].val[i]=1LL*tree[p<<1|1].val[i]*tree[p].lazy%MOD;
}
tree[p<<1].lazy=1LL*tree[p<<1].lazy*tree[p].lazy%MOD;
tree[p<<1|1].lazy=1LL*tree[p<<1|1].lazy*tree[p].lazy%MOD;
tree[p].lazy=1;
}
void update_times2(int L,int R,int p) {
if(tree[p].L==L&&tree[p].R==R) {
for(int i=0;i<=m;i++)tree[p].val[i]=(tree[p].val[i]+tree[p].val[i])%MOD;
tree[p].lazy=(tree[p].lazy+tree[p].lazy)%MOD;
return;
}
Down(p);
int mid=(tree[p].L+tree[p].R)>>1;
if(R<=mid)update_times2(L,R,p<<1);
else if(L>=mid+1)update_times2(L,R,p<<1|1);
else update_times2(L,mid,p<<1),update_times2(mid+1,R,p<<1|1);
Up(p);
}
node Query(int L,int R,int p) {
if(tree[p].L==L&&tree[p].R==R)return tree[p];
Down(p);
int mid=(tree[p].L+tree[p].R)>>1;
if(R<=mid)return Query(L,R,p<<1);
else if(L>=mid+1)return Query(L,R,p<<1|1);
else {
node Ans,S1=Query(L,mid,p<<1),S2=Query(mid+1,R,p<<1|1);
for(int i=0;i<=m;i++)Ans.val[i]=(S1.val[i]+S2.val[i])%MOD;
return Ans;
}
}
void Set(int pos,int p) {
if(tree[p].L==tree[p].R) {
for(int i=0;i<=m;i++)tree[p].val[i]=dp[pos][i];
return;
}
Down(p);
int mid=(tree[p].L+tree[p].R)>>1;
if(pos<=mid)Set(pos,p<<1);
else Set(pos,p<<1|1);
Up(p);
}
void work() {
init();
build(1,n,1);
for(register int i=1; i<=n; ++i) {
memset(res.val,0,sizeof(res.val));
if(A[i]+1<=n)update_times2(A[i]+1,n,1);
if(A[i]-1>=1)res=Query(1,A[i]-1,1);
for(register int j=0; j<=m; ++j) {
dp[A[i]][j]=1;
for(register int k=j; k>=0; --k) {
dp[A[i]][j]=(dp[A[i]][j]+1LL*C[j][k]*res.val[k]%MOD)%MOD;
}
}
Set(A[i],1);
}
printf("%d",Query(1,n,1).val[m]);
}
}