解析
确实精妙的一道题。
卡在最后一步dp转化上了,这个转化也确实是本题的难点。
当一个计数难以下手的时候,先想想如何判合法?
不难想到一个较为显然的贪心:假如和上一个一样,就填一个当前的最大值和最小值;否则,不妨设变大,如果 (bi,bi+1)(b_i,b_{i+1})(bi,bi+1) 之间已经有数,必然非法,否则,若 bi+1b_{i+1}bi+1 未出现,填它和最大值,否则填最大和次大值。
把这个过程抽象为判定合法的条件:
- bi∈[ai,a2n−i]b_i\in [a_i,a_{2n-i}]bi∈[ai,a2n−i]
- ∄j<i,bj∈(bi−1,bi)\not \exists j<i,b_j\in (b_{i-1},b_i)∃j<i,bj∈(bi−1,bi)
第一条容易限制,关键是第二条。
尝试对问题进行等价转化:
倒序考虑,每次把合法区间的数排序并去重,并且把所有被 (ai,ai+1)(a_i,a_{i+1})(ai,ai+1) 夹在中间的数删去,剩下的数就是下次可以填的数。
进一步的,可以看成维护一个集合 SSS,第 i 次加入两个元素 ai,a2n−ia_i,a_{2n-i}ai,a2n−i,然后把集合内 (ai,ai+1)(a_i,a_{i+1})(ai,ai+1) 之间的元素删除。
这个就容易 dp 了。设 fi,l,rf_{i,l,r}fi,l,r 表示填第 i 位的时候集合内有 lll 个小于中位数的元素,rrr 个大于中位数的元素的方案数,枚举下一次中位数的位置转移即可。
复杂度 O(n4)O(n^4)O(n4)。
注意不要向集合内加入大小相同的元素。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;const int N=105;
const int mod=1e9+7;
inline ll read(){ll x(0),f(1);char c=getchar();while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f;
}inline ll ksm(ll x,ll k){ll res(1);while(k){if(k&1) res=res*x%mod;x=x*x%mod;k>>=1;}return res;
}int n,m;
int a[N<<1];
int f[N][N][N];
#define add(x,y) (x+=y,x>=mod?x-=mod:0)signed main(){#ifndef ONLINE_JUDGEfreopen("a.in","r",stdin);freopen("a.out","w",stdout);#endifn=read();for(int i=1;i<=2*n-1;i++) a[i]=read();sort(a+1,a+1+2*n-1);f[n][0][0]=1;ll ans(0);for(int i=n-1;i>=1;i--){int dl=(a[i]!=a[i+1]),dr=(a[2*n-i]!=a[2*n-i-1]);for(int l=0;l<=2*n-1;l++){for(int r=0;l+r<=2*n-1;r++){if(!f[i+1][l][r]) continue;add(f[i][l+dl][r+dr],f[i+1][l][r]);for(int k=1;k<=l+dl;k++){add(f[i][l+dl-k][r+dr+1],f[i+1][l][r]);}for(int k=1;k<=r+dr;k++){add(f[i][l+dl+1][r+dr-k],f[i+1][l][r]);}}}}for(int l=0;l<=2*n-1;l++){for(int r=0;l+r<=2*n-1;r++) add(ans,f[1][l][r]);}printf("%lld\n",ans);return 0;
}