【Luogu】 [ABC313Ex] Group Photo

题目链接

Atcoder方向
Luogu方向

题目解法

神仙 d p dp dp
考虑序列 c i = m i n ( a i , a i − 1 ) c_i=min(a_i,a_{i-1}) ci=min(ai,ai1) c 1 = a 1 , c n + 1 = a n c_1=a_1,c_{n+1}=a_n c1=a1,cn+1=an,如果合法,必然满足 b i > c i b_i>c_i bi>ci
若将 c i c_i ci 升序排列, b i b_i bi 升序排列,结果仍然成立
b b b 升序排列, a a a 从小到大考虑
这里有一个神奇的连通块做法:
d p i , j dp_{i,j} dpi,j 为前 i i i a a a,构成了 j j j 个连通块的方案数
考虑分 3 类情况:

  1. i + 1 i+1 i+1 加在一个连通块的左边或右边,显然 d p i + 1 , j + = d p i , j ∗ 2 j dp_{i+1,j}+=dp_{i,j}*2j dpi+1,j+=dpi,j2j
    考虑限制条件,仅考虑加在右边的情况,那么加的位置的 c c c 为之前考虑过的数,现在只需要考虑后面一个数。注意到对于一个连通块,会影响到当前连通块及后一个元素,所以需要满足的条件为 a i + 1 < b i + j + 1 a_{i+1}ai+1<bi+j+1
  2. i + 1 i+1 i+1 连接 2 个连通块, d p i + 1 , j − 1 + = d p i , j ∗ ( j − 1 ) dp_{i+1,j-1}+=dp_{i,j}*(j-1) dpi+1,j1+=dpi,j(j1),根据之前的思考方法,这里没有限制
  3. i + 1 i+1 i+1 新建 1 个连通块, d p i + 1 , j + 1 + = d p i , j ∗ ( j + 1 ) dp_{i+1,j+1}+=dp_{i,j}*(j+1) dpi+1,j+1+=dpi,j(j+1)
    新建的 1 个可以影响到当前的位置与后一个位置,因为 b b b 从小到大排序过了,所以这里的限制为 a i < b i + j + 1 a_iai<bi+j+1

时间复杂度 O ( n 2 ) O(n^2) O(n2)

#include 
using namespace std;
const int N=5100,P=998244353;
int n,a[N],b[N],dp[N][N];
inline int read(){
    int FF=0,RR=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
    for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
    return FF*RR;
}
int main(){
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=n+1;i++) b[i]=read();
    sort(b+1,b+n+2);
    sort(a+1,a+n+1);
    dp[0][0]=1;
    for(int i=0;i<n;i++)
        for(int j=0;j<=i;j++){
            //放在1个连续段的左侧或右侧
            if(a[i+1]<b[i+j+1]) dp[i+1][j]=(dp[i+1][j]+1ll*dp[i][j]*2*j)%P;
            //连接2个连续段
            if(j>1) dp[i+1][j-1]=(dp[i+1][j-1]+1ll*dp[i][j]*(j-1))%P;
            //新建1个连续段
            if(a[i+1]<b[i+j+1]) dp[i+1][j+1]=(dp[i+1][j+1]+1ll*dp[i][j]*(j+1))%P;
            // cout<
        }
    printf("%d",dp[n][1]);
    return 0;
}

你可能感兴趣的:(Atcoder,算法)