题目描述:
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;
}