hdu5322 三次多校1007


题目描述:

10000组测试数据,n(1≤n≤100000). 意思是n的所有全排列. 对于每一种全排列的价值这样定义:For each Ai, if there exists a minimum j satisfies j>i and Aj>Ai , then connect an edge between Ai and Aj. 然后看有几个联通块,数一数每个联通块内部的点的个数.然后把这些个数乘起来得到p.那么p^2就是这个排列的价值. 问这么多全排列的价值的和.

题解:

首先是分析题意,这样要直接考虑给定一个i,所有排列i对答案的贡献度.而不是一种一种排列去算. 算dp[n], 要缩小规模.发现n其实很特殊.n放在i的话,那么1~i的一定是一个联通块(这个是一个重点,i包括i以前的是一个特殊情况).i之后的n-i是一个子问题, 可以表示为dp[n-i],然后加上组合数,加上阶乘,能够写出来式子. dp[n] = sum(dp[n-j]/(n-j)! * j^j)(j从1到n). 剩下是两种方法快速求dp.首先(n-j )!可以无视.带上dp整体设为f[n-j].
法一:观察f[n-j]的系数:1 4 9 16…. 差分 3 5 7… 再差分 2 2 2 2….仔细一点维护3个sum值就可以o(1)的dp.
法二:将f[n-j]看做一部分, j^j 看做一部分, 发现对于n的贡献可以看做是一个卷积,次数和为n的j和n-j的权重系数的相乘. 但是我们并不知道前一部分的dp值. 这里就用到了cdq分治. dp1 到 n .那么取一个mid,先递归求1到mid, 然后考虑1到mid 对mid+1到n部分的影响.之后在影响的基础上再做mid+1到n的递归. 影响怎么算? 就可以用到卷积的和为n的性质. 构造用fft. 两边系数分别是f[i]和j*j. 然后乘起来, 把结果中次数为i的对应到mid+1的后面. 次数对应是几以及构造fft式子时候的细节认真处理下.另外要用到ntt. 对于这个mod,我们可以用他的原根3来搞.

重点:

1.考虑对答案的直接贡献.
2.dp缩小规模.并且全排列想到n的位置,特殊处理.
3.n放在i位,那么i和i之前的是一个已经定了的特殊情况,不是子问题. i之后的才是一个独立的子问题.
3.推导公式中有阶乘有组合数.把组合数拆掉和阶乘搞到一起.可以化简.
4.最后的递推式两种加速方式.

代码:
//o(1)递推版本.
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <ctype.h>
#include <limits.h>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <stack>
#include <set>
#include <bitset>
#define CLR(a) memset(a, 0, sizeof(a))
#define REP(i, a, b) for(ll i = a;i < b;i++)
#define REP_D(i, a, b) for(ll i = a;i <= b;i++)

typedef long long ll;

using namespace std;

const ll maxn = 100000 + 100;
const ll MOD = 998244353;

ll dp[maxn], f[maxn];
ll n;
ll jc[maxn], jcrev[maxn];

ll pow_mod(ll x,ll n)
{
    ll res = 1;
    ll t = x%MOD;
    while(n)
    {
        if(n&1)
            res = (res*t)%MOD;
        t = (t*t)%MOD;
        n >>= 1;
    }
    return res;
}

void getJC()
{
    jc[0] = 1;
    jcrev[0] = 1;
    for(ll i = 1;i<=100000;i++)
    {
        jc[i] = (jc[i-1]*i)%MOD;
        jcrev[i] = (jcrev[i-1]*pow_mod(i, MOD-2))%MOD;
//        if(i <= 4)
//        {
//            printf("i is %I64d    jc  %I64d  jcrev %I64d\n", i, jc[i], jcrev[i]);
//        }
    }
}

void getDp()
{
    dp[0] = 1;
    f[0] = 1;
    ll sum, sum_1, sum_1_1;
    sum = 0;
    sum_1 = 0;
    sum_1_1 = 0;
    REP_D(i, 1, 100000)
    {
        if(i == 1)
        {
            sum_1_1 = f[0];
        }
        else
        {
            sum_1_1 = (sum_1_1 + f[i-1])%MOD;
            sum_1_1 = (sum_1_1 + f[i-2])%MOD;
        }
        sum_1 = (sum_1 + sum_1_1)%MOD;
        sum = (sum + sum_1)%MOD;
        dp[i] = (sum*jc[i-1])%MOD;
        f[i] = (dp[i]*jcrev[i])%MOD;
    }
}

int main()
{
    //freopen("7Gin.txt", "r", stdin);
    //freopen("7Gout.txt", "w", stdout);
    getJC();
    getDp();
    while(scanf("%I64d", &n) != EOF)
    {
        printf("%I64d\n", dp[n]);
    }
    return 0;
}
//ntt转移卷积加速.
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <ctype.h>
#include <limits.h>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <stack>
#include <set>
#include <bitset>
#define CLR(a) memset(a, 0, sizeof(a))
#define REP(i, a, b) for(ll i = a;i < b;i++)
typedef long long ll;

using namespace std;

const ll M = 998244353;
const ll maxn = 5e5 + 100;
ll dp[maxn];
ll jc[maxn], jcrev[maxn];
ll x1[maxn],x2[maxn];
/*
* 进行FFT和IFFT前的反转变换。
* 位置i和 (i二进制反转后位置)互换
* len必须去2的幂
*/
void change(ll y[],ll len)
{
    ll i,j,k;
    for(i = 1, j = len/2; i <len-1; i++)
    {
        if(i < j)swap(y[i],y[j]);
//交换互为小标反转的元素,i<j保证交换一次
//i做正常的+1,j左反转类型的+1,始终保持i和j是反转的
        k = len/2;
        while(j >= k)
        {
            j -= k;
            k /= 2;
        }
        if(j < k)j += k;
    }
}
/*
* 做FFT
* len必须为2^k形式,
* on==1时是DFT,on==-1时是IDFT
*/

ll power(ll a, ll n)
{
    ll res = 1, z = a;
    while (n)
    {
        if (n & 1) res = res * z % M;
        z = z * z % M;
        n >>= 1;
    }
    return res;
}

ll inverse(ll x)
{
    return power(x, M - 2);
}

void getJc()
{
    jc[0] = 1;
    jcrev[0] = inverse(1);
    for(ll i = 1;i <= 100000;i++)
    {
        jc[i] = jc[i-1]*i%M;
        jcrev[i] = inverse(jc[i]);
//        if(i < 10)
//        {
//            printf("i is %I64d  jcrev is %I64d\n", i, jcrev[i]);
//        }
    }
}

void fft(ll y[],ll len,ll on)//from 0 to len - 1;
{
    change(y,len);
    for(ll h = 2; h <= len; h <<= 1)
    {
        ll unit_p0 = power(3, (M-1)/(h));//这个地方是重点
        if(on == -1)
        {
            unit_p0 = inverse(unit_p0);
        }
        //Complex wn(cos(-on*2*PI/h),sin(-on*2*PI/h));
        for(ll j = 0; j < len; j+=h)
        {
            ll unit = 1;
            for(ll k = j; k < j+h/2; k++)
            {
                ll u = y[k];
                ll t = unit*y[k+h/2]%M;
                //Complex u = y[k];
                //Complex t = w*y[k+h/2];
                y[k] = u+t;
                if(y[k] >= M)
                {
                    y[k] -= M;
                }
                if(u < t)
                {
                    u += M;
                }
                y[k+h/2] = u-t;
                //y[k+h/2] = (u-t)%M + ;
                // = w*wn;
                unit = unit*unit_p0%M;
            }
        }
    }

    if(on == -1)
    {
        ll key = inverse(len);
        for(ll i = 0; i < len; i++)
            y[i] = y[i]*key%M;
    }
}


void divid(ll l, ll r)
{
    if(l==r)
    {
        return ;
    }
    ll mid = (l+r)/2;
    divid(l, mid);
    ll len = r-l+1;
    ll len1 = 1;
    while(len1<2*len)
    {
        len1 <<= 1;
    }
    for(ll i = 0;i<len1;i++)
    {
        x1[i] = 0;
        x2[i] = 0;
        if(i+l <= mid)
        {
            x1[i] = dp[i+l]*jcrev[i+l]%M;
        }
        if(i<=len)
        {
            x2[i] = i*i%M;
        }
    }
    fft(x1, len1, 1);
    fft(x2, len1, 1);
    for(ll i = 0;i<len1;i++)
    {
        x1[i] = (x1[i]*x2[i])%M;
    }
    fft(x1, len1, -1);
    for(ll i = mid+1;i<=r;i++)
    {
        dp[i] = (dp[i] + jc[i-1]*x1[i-l]%M)%M;
    }
    divid(mid+1, r);
}
ll n;
int main()
{
   // freopen("12Lin.txt", "r", stdin);
    //freopen("1out.txt", "w", stdout);
    getJc();
    CLR(dp);
    dp[0] = 1;
    divid(0, 100000);
//    for(int i = 1;i<=20;i++)
//    {
//        printf("%d ", dp[i]);
//    }
    //printf("\n");
    while(scanf("%I64d", &n) != EOF)
    {
        printf("%I64d\n", dp[n]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值