AT2366 [AGC012F] Prefix Median(dp)

解析

确实精妙的一道题。
卡在最后一步dp转化上了,这个转化也确实是本题的难点。

当一个计数难以下手的时候,先想想如何判合法?
不难想到一个较为显然的贪心:假如和上一个一样,就填一个当前的最大值和最小值;否则,不妨设变大,如果 ( b i , b i + 1 ) (b_i,b_{i+1}) (bi,bi+1) 之间已经有数,必然非法,否则,若 b i + 1 b_{i+1} bi+1 未出现,填它和最大值,否则填最大和次大值。

把这个过程抽象为判定合法的条件:

  1. b i ∈ [ a i , a 2 n − i ] b_i\in [a_i,a_{2n-i}] bi[ai,a2ni]
  2. ∄ j < i , b j ∈ ( b i − 1 , b i ) \not \exists j<i,b_j\in (b_{i-1},b_i) j<i,bj(bi1,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,a2ni,然后把集合内 ( 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;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值