ICPCCamp2016day3F.The Jump Address

链接:等camp过后挂出来再加

题意:给定n,k,对于所有的n的排列P1,P2...Pn,若Px>Px+1则给这个排列的权值+x,求权值为k的n的排列有多少种。n<=400,k<=n*(n-1)/2。

分析:如果强行dp会发现只能有n^4的状态和复杂度。出题人的思路很巧妙,但是由于本身具有规律,被很多人找规律过了2333。这个问题可以转换为求所有n的排列中逆序对个数=k的排列有多少种。怎么证明呢?我们知道原题中如果之前已经放好i个数,当加入i+1个数的时候可能贡献0或i。那么如果我们转换的求排列的逆序对个数也要对应+0/+i,我们这样来理解这个过程,我对+i这个贡献举例。比如这i+1个数是这样的####%###%###@,最后加入的是@,%表示的是比@大的数,#表示的是比@小的数字,那么现在我们逆序对个数的贡献就+2了,但是我们还缺少10个#的逆序对贡献没有加,那么我们映射成%####%###@###这个排列就可以了,很显然这样的排列一一映射后是同样的方案数的,那么我们就可以设dp[i][j]表示加入1~i个数逆序对数为j的方案数,那么我们的转移是dp[i][j]=dp[i-1][j]+dp[i-1][j-1]+dp[i-1][j-2]+....+dp[i-1][max(0,j-i+1)]。

代码:

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<math.h>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
const int N=410;
const int MAX=151;
const int MOD1=100000007;
const int MOD2=100000009;
const int INF=2100000000;
const double EPS=0.00000001;
typedef long long ll;
const ll MOD=1000000007;
typedef unsigned long long uI64;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
ll dp[N][N*N],sum[N*N];
int main()
{
    int i,j,g,n,k,mx;
    scanf("%d%d", &n, &k);
    memset(dp,0,sizeof(dp));
    dp[1][0]=sum[0]=sum[1]=1;
    for (i=2;i<=n;i++) {
        g=i*(i-1)/2;
        for (j=0;j<=k&&j<=g;j++) {
            mx=max(0,j-i+1);
            if (mx==0) dp[i][j]=sum[j];
            else dp[i][j]=(sum[j]-sum[mx-1])%MOD;
        }
        sum[0]=dp[i][0];
        for (j=1;j<=k;j++) sum[j]=(sum[j-1]+dp[i][j])%MOD;
    }
    printf("%lld\n", (dp[n][k]+MOD)%MOD);
    return 0;
}

/*
5 5
10 10

22
21670
*/


你可能感兴趣的:(ICPCCamp2016day3F.The Jump Address)