luogu P8859 冒泡排序
首先\(type=1\)的情况是平凡的,设可以发现一个数不需要被操作当且仅当这个数前面的数都小于这个数。可以设计出这样的dp:设\(f_{i,j}\)表示到了第\(i\)个数,前面有\(j\)个数需要操作的方案数,则有两种平凡。
然后\(type=2\)的情况下我就陷入误区了,以为一定要将\(1\)单独处理,先把\(1\)的位置定下来断环为链。但是实际上这样使得这个结构变得不优美。正确的姿势是钦定一条边不能过然后断环为链,然后当成排列做。
但是还是不是很好做。实际上不需要操作的就是前缀最大值,这个在笛卡尔树上表现出来就是一条从根节点开始的左链,然后现在能把左边的一些节点扔到右边去,问所有情况下的最大值。这实际上就是每个点走到根路径上左边的最大值。可以对树计数做到\(O(n^4)\),前缀和变成\(O(n^3)\)。
#include<bits/stdc++.h>
#define Gc() getchar()
#define Me(x,y) memset(x,y,sizeof(x))
#define Mc(x,y) memcpy(x,y,sizeof(x))
#define d(x,y) ((m)*(x-1)+(y))
#define R(n) (rnd()%(n))
#define Pc(x) putchar(x)
#define LB lower_bound
#define UB upper_bound
#define PB push_back
using ll=long long;using db=double;using lb=long db;using ui=unsigned;using ull=unsigned ll;
using namespace std;const int N=5e2+5,M=10+5,K=1e5+5,mod=1e9+7,Mod=mod-1,INF=2e9+7;const db eps=1e-5;mt19937 rnd(263082);
int n,k,op;
namespace Solve1{
ll dp[N][N];void Solve(){
int i,j;dp[0][0]=1;for(i=1;i<=n;i++){
for(j=0;j<=i;j++)dp[i][j]=dp[i-1][j],j&&(dp[i][j]=(dp[i][j]+dp[i-1][j-1]*(i-1))%mod);
}printf("%lld\n",dp[n][k]);
}
}
namespace Solve2{
ll dp[N][N],Q[N][N],C[N][N];void Solve(){
int i,j,x,y;for(i=0;i<=n;i++) for(C[i][0]=j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
dp[0][0]=1;for(i=0;i<=n;i++) Q[0][i]=1;for(i=1;i<n;i++){
for(j=0;j<i;j++){
for(x=0;x<=j;x++) dp[i][x+1]+=dp[j][x]*Q[i-j-1][x]%mod*C[i-1][j]%mod;
for(y=1;y<=i-j-1;y++) dp[i][y]+=Q[j][y-1]*dp[i-j-1][y]%mod*C[i-1][j]%mod;
}
for(j=1;j<=n;j++) dp[i][j]%=mod,Q[i][j]=(Q[i][j-1]+dp[i][j])%mod;
} printf("%lld\n",dp[n-1][n-k-1]);
}
}
int main(){
freopen("1.in","r",stdin);
scanf("%d%d%d",&n,&k,&op);if(op==1) Solve1::Solve();else Solve2::Solve();
}