ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

「LibreOJ NOI Round #2」不等关系 (dp+NTT分治)

2021-02-15 19:59:05  阅读:239  来源: 互联网

标签:cnt NOI LibreOJ int NTT len maxn dp mod


description

戳我看题目哦

solution

有一道非常相似的题目

一棵树,每条边限制两个端点的大小关系(限制 a [ u ] > a [ v ] a[u]>a[v] a[u]>a[v] 或 a [ u ] < a [ v ] a[u]<a[v] a[u]<a[v])
求有多少种符合要求的排列 a a a满足整棵树的限制。 n < = 5000 n<=5000 n<=5000

考虑如果所有边都是朝一个方向的话很好做
答案就是 n ! n! n!除以每个子树的大小
如果存在反向边的话,暴力枚举断开若干个反向边,剩下的边改为正向,然后计算答案
容斥即可。这样暴力做的复杂度是 O ( 2 n ∗ n ) O(2^n*n) O(2n∗n) 的
考虑 d p dp dp, f ( i , j , k ) f(i,j,k) f(i,j,k) 表示以 i i i 为根的子树,当前 i i i 所在连通块内有 j j j 个点,总共反向 k k k 条边的方案数
合并两棵子树时,如果边是正向的,那么直接合并;
否则要么断开,要么让 k + 1 k+1 k+1 并且按照正向合并
复杂度 n n n 的若干次方
考虑最后的容斥只需要关注 k k k 的奇偶性,因此第三维完全可以省掉
即合并两棵子树时,如果边是正向则直接合并,否则值就是断开的方案减掉把边正向的方案
因此就是一个简单的树背包,复杂度 O ( n 2 ) O(n^2) O(n2)

此题只是需要将二维 d p dp dp再次优化即可
设 d p [ i ] dp[i] dp[i]表示前缀 i i i的合法方案数, c n t [ i ] cnt[i] cnt[i]表示前缀 i i i中 > > >的个数
d p [ i ] i ! = ∑ j = 0 i − 1 [ s [ j ] = ′ > ′ ] ( i − j ) ! ( − 1 ) c n t [ i − 1 ] − c n t [ j ] × d p [ j ] j ! \frac{dp[i]}{i!}=\sum_{j=0}^{i-1}\frac{[s[j]='>']}{(i-j)!}(-1)^{cnt[i-1]-cnt[j]}\times \frac{dp[j]}{j!} i!dp[i]​=j=0∑i−1​(i−j)![s[j]=′>′]​(−1)cnt[i−1]−cnt[j]×j!dp[j]​
将 ( − 1 ) c n t [ i − 1 ] (-1)^{cnt[i-1]} (−1)cnt[i−1]提出来,剩余部分用 N T T NTT NTT分治完成
有难度
在这里插入图片描述

code

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define mod 998244353
#define int long long 
#define maxn 400005
int len, inv;
char s[maxn];
int cnt[maxn];
int fac[maxn], ifac[maxn], r[maxn];
int f[maxn], g[maxn], dp[maxn];

int qkpow( int x, int y ) {
	int ans = 1;
	while( y ) {
		if( y & 1 ) ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

void NTT( int *c, int opt ) {
	for( int i = 0;i < len;i ++ )
		if( i < r[i] ) swap( c[i], c[r[i]] );
	for( int i = 1;i < len;i <<= 1 ) {
		int omega = qkpow( opt == 1 ? 3 : mod / 3 + 1, ( mod - 1 ) / ( i << 1 ) );
		for( int j = 0;j < len;j += ( i << 1 ) ) {
			int w = 1;
			for( int k = 0;k < i;k ++, w = w * omega % mod ) {
				int x = c[j + k], y = w * c[j + k + i] % mod;
				c[j + k] = ( x + y ) % mod;
				c[j + k + i] = ( x - y + mod ) % mod;
			}
		}
	}
	if( opt == -1 ) {
		int inv = qkpow( len, mod - 2 );
		for( int i = 0;i < len;i ++ )
			c[i] = c[i] * inv % mod;
	}
}

void solve( int L, int R ) {
	if( L == R ) {
		if( ! L ) dp[L] = 1;
		else dp[L] = cnt[L] & 1 ? mod - dp[L] : dp[L];//单独提出来 
		return;
	}
	int mid = ( L + R ) >> 1;
	solve( L, mid );
	len = 1; int l = 0;
	while( len <= R - L + 1 + mid - L ) len <<= 1, l ++;
	for( int i = 0;i < len;i ++ )
		r[i] = ( r[i >> 1] >> 1 ) | ( ( i & 1 ) << ( l - 1 ) );
	for( int i = 0;i <= mid - L;i ++ )
		if( s[i + L] == '<' && i + L != 0 ) f[i] = 0;
		else f[i] = cnt[i + L] & 1 ? dp[i + L] : mod - dp[i + L];//注意奇偶转换 
	for( int i = mid - L + 1;i < len;i ++ ) f[i] = 0;
	for( int i = 0;i <= R - L + 1;i ++ ) g[i] = ifac[i];
	for( int i = R - L + 2;i < len;i ++ ) g[i] = 0;
	NTT( f, 1 );
	NTT( g, 1 );
	for( int i = 0;i < len;i ++ ) f[i] = f[i] * g[i] % mod;
	NTT( f, -1 );
	for( int i = mid + 1;i <= R;i ++ ) dp[i] = ( dp[i] + f[i - L] ) % mod;
	solve( mid + 1, R );
}

signed main() {
	scanf( "%s", s + 1 );	
	int n = strlen( s + 1 );
	s[++ n] = '>';
	fac[0] = 1;
	for( int i = 1;i <= n;i ++ )
		fac[i] = fac[i - 1] * i % mod;
	ifac[n] = qkpow( fac[n], mod - 2 );
	for( int i = n - 1;~ i;i -- )
		ifac[i] = ifac[i + 1] * ( i + 1 ) % mod;
	for( int i = 1;i <= n;i ++ )
		cnt[i] = cnt[i - 1] + ( s[i] == '>' );
	solve( 0, n );
	printf( "%lld\n", dp[n] * fac[n] % mod );
	return 0;
}

标签:cnt,NOI,LibreOJ,int,NTT,len,maxn,dp,mod
来源: https://blog.csdn.net/Emm_Titan/article/details/113818488

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有