解析
确实精妙的一道题。
卡在最后一步dp转化上了,这个转化也确实是本题的难点。
当一个计数难以下手的时候,先想想如何判合法?
不难想到一个较为显然的贪心:假如和上一个一样,就填一个当前的最大值和最小值;否则,不妨设变大,如果
(
b
i
,
b
i
+
1
)
(b_i,b_{i+1})
(bi,bi+1) 之间已经有数,必然非法,否则,若
b
i
+
1
b_{i+1}
bi+1 未出现,填它和最大值,否则填最大和次大值。
把这个过程抽象为判定合法的条件:
- b i ∈ [ a i , a 2 n − i ] b_i\in [a_i,a_{2n-i}] bi∈[ai,a2n−i]
- ∄ j < i , b j ∈ ( b i − 1 , b i ) \not \exists j<i,b_j\in (b_{i-1},b_i) ∃j<i,bj∈(bi−1,bi)
第一条容易限制,关键是第二条。
尝试对问题进行等价转化:
倒序考虑,每次把合法区间的数排序并去重,并且把所有被
(
a
i
,
a
i
+
1
)
(a_i,a_{i+1})
(ai,ai+1) 夹在中间的数删去,剩下的数就是下次可以填的数。
进一步的,可以看成维护一个集合
S
S
S,第 i 次加入两个元素
a
i
,
a
2
n
−
i
a_i,a_{2n-i}
ai,a2n−i,然后把集合内
(
a
i
,
a
i
+
1
)
(a_i,a_{i+1})
(ai,ai+1) 之间的元素删除。
这个就容易 dp 了。设
f
i
,
l
,
r
f_{i,l,r}
fi,l,r 表示填第 i 位的时候集合内有
l
l
l 个小于中位数的元素,
r
r
r 个大于中位数的元素的方案数,枚举下一次中位数的位置转移即可。
复杂度
O
(
n
4
)
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_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
n=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;
}