题解 P2159 【[SHOI2009]舞会】
不错的DP题。但是需要高精就很恶心……
原题
本文更像是对Thyer大佬题解的解释。因为我们的做法高度相似,但是Thyer大佬的题解不太详细,于是就有了此文。
观察数据范围,
首先想到将男女按身高排序,得到一些优秀的性质。具体见后文。
然后,考虑固定男人的位置不变,接着把女人一个一个与男人配对。
那么,令
答案为
尝试转移:
如果当前女人不高于男人:
1:当前女人不会使
此时,当前女人可以放到任意满足男人身高大于等于当前女人身高的位置上,同时将该位置上的女人放到当前位置上,形成新的方案。
记满足这样要求的位置有
或者,可以将当前女人放到任意原来就是女伴更高的位置。由于女人身高已经过排序,交换后“原来就是女伴更高的位置”仍然是女伴更高,而当前位置也仍然是男人不矮于女人。
满足这样要求的位置显然有
同时,
综上,对于
2:当前女人会使
这个简单,用总位置数
得到“当前女人不高于男人”的状态转移方程:
那么如果当前女人高于男人:
1:当前女人不会使
此时她无论放到哪里都会形成一组“女伴更高”的组合。(因为男人已经按身高排序)那么不会使
也就是“原来女伴更高的部分”减去高于当前男人的部分。
那么记之前的女伴中,高于当前男人的女人有
2:当前女人会使
用总位置数
得到“当前女人高于男人”的状态转移方程:
由于
如果对细节感到好奇,或者想白嫖高精的,见代码。
#include <bits/stdc++.h>
using namespace std;
const int maxn=205;
int n,k;int M[maxn],W[maxn];
struct Int{
#define max_size (501)
int val[max_size];int len;const int base=10;//压位的部分就先不管了
Int(int _val=0){len=1;for(int i=0;i<max_size;i++) val[i]=0;*this=_val;}
Int(const char s[max_size]){init(s);}
Int(char s[max_size]){init(s);}
int& operator[](const int id){return val[id];}
void operator=(int _val){
for(int i=0;i<=len;i++) val[i]=0;
len=0;while(_val) val[++len]=_val%10,_val/=10;
}
void operator=(Int pos){
*this=0;len=pos.len;
for(int i=1;i<=len;i++) val[i]=pos.val[i];
}
void carry_bit(){
for(int i=1;i<=len;i++){
if(val[i]>9){
val[i+1]+=val[i]/10;
val[i]%=10;
if(i==len) len++;
}
}
}
void eat_zero(){
for(;!val[len]&&len>1;len--);
if(!len) len=0,val[len]=0;
}
Int operator+(int _val){
Int now=*this;now[1]+=_val;
now.carry_bit();return now;
}
Int operator+(Int _val){
Int now=*this;now.len=max(now.len,_val.len);
for(int i=1;i<=_val.len;i++) now[i]+=_val[i];
now.carry_bit();return now;
}
Int operator*(int _val){
Int now=*this;
for(int i=1;i<=len;i++) now[i]*=_val;
now.carry_bit();return now;
}
Int operator*(Int _val){
Int now;now.len=len+_val.len-1;
for(int i=1;i<=now.len;i++){
for(int j=1;j<=_val.len;j++){
now[i+j-1]+=val[i]*_val[j];
}
}
now.eat_zero();
now.carry_bit();return now;
}
Int operator/(int _val){
Int now=*this;
for(int i=len;i>=1;i--){
if(i) now[i-1]+=base*(now[i]%_val);
now[i]/=_val;
}
now.eat_zero();return now;
}
bool operator<(Int _val)const{
if(len!=_val.len) return len<_val.len;
for(int i=len;i>=1;i--){
if(val[i]!=_val[i]) return val[i]<_val[i];
}
return 0;
}
bool operator==(Int _val)const{
if(len!=_val.len) return 0;
for(int i=len;i>=1;i--){
if(val[i]!=_val[i]) return 0;
}
return 1;
}
bool operator>(Int _val)const{
if(len!=_val.len) return len>_val.len;
for(int i=len;i>=1;i--){
if(val[i]!=_val[i]) return val[i]>_val[i];
}
return 0;
}
bool operator<=(Int _val)const{return !(*this>_val);}
bool operator>=(Int _val)const{return !(*this<_val);}
void init(const char s[max_size]){
len=0;int begin=0;
for(int i=1;s[i]>='0'&&s[i]<='9';i++){
if(!begin&&s[i]==48) continue;
if(!begin){begin=i;}len++;
}
if(!begin){len=0,val[1]=0;return;}
for(int i=1;i<=len;i++){
val[i]=s[len-i+begin]-48;
}
}
void operator=(const char s[max_size]){this->init(s);}
void print(){
eat_zero();if(!len) putchar(48);
else for(int i=len;i>=1;i--) putchar(val[i]+48);
putchar('\n');
}
};
//这个高精板子并不太靠谱……并且不支持负数
//如果你还是想用的话,那我只能说:
//El Psy Congroo.
Int dp[2][maxn];
int main(){
// freopen("P2159_1.in","r",stdin);
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&M[i]);
for(int i=1;i<=n;i++) scanf("%d",&W[i]);
std::sort(M+1,M+n+1),std::sort(W+1,W+n+1);
dp[0][0]=1;
for(int i=1;i<=n;i++){
int Alpha=0,Sg=i&1;
for(int j=0;j<=i;j++) dp[Sg][j]=0;
if(M[i]>=W[i]){
for(int j=1;j<=i;j++) if(M[j]>=W[i]) Alpha++;
for(int j=0;j<=i;j++)
dp[Sg][j]=dp[!Sg][j]*(j+Alpha);
for(int j=1;j<=i-Alpha;j++)
dp[Sg][j]=(dp[Sg][j]+dp[!Sg][j-1]*(i-Alpha-(j-1)));
}
else{
for(int j=1;j< i;j++) if(W[j]>M[i]) Alpha++;//注意不要取等号,因为这里Alpha统计的是在i之前的
for(int j=1;j<=i;j++)
dp[Sg][j]=dp[Sg][j]+dp[!Sg][j-1]*(i-((j-1)-Alpha));
for(int j=Alpha;j<=i;j++)
dp[Sg][j]=dp[Sg][j]+dp[!Sg][j]*(j-Alpha);
}
}
Int answer=0;
for(int i=0;i<=k;i++) answer=answer+dp[n&1][i];
answer.print();
return 0;
}