「LibreOJ NOI Round #2」不等关系 (dp+NTT分治)
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])
求有多少種符合要求的排列aaa滿足整棵樹的限制。n<=5000n<=5000n<=5000
考慮如果所有邊都是朝一個方向的話很好做
答案就是n!n!n!除以每個子樹的大小
如果存在反向邊的話,暴力枚舉斷開若干個反向邊,剩下的邊改為正向,然后計算答案
容斥即可。這樣暴力做的復雜度是 O(2n?n)O(2^n*n)O(2n?n) 的
考慮 dpdpdp,f(i,j,k)f(i,j,k)f(i,j,k) 表示以 iii 為根的子樹,當前 iii 所在連通塊內有 jjj 個點,總共反向 kkk 條邊的方案數
合并兩棵子樹時,如果邊是正向的,那么直接合并;
否則要么斷開,要么讓 k+1k+1k+1 并且按照正向合并
復雜度 nnn 的若干次方
考慮最后的容斥只需要關注 kkk 的奇偶性,因此第三維完全可以省掉
即合并兩棵子樹時,如果邊是正向則直接合并,否則值就是斷開的方案減掉把邊正向的方案
因此就是一個簡單的樹背包,復雜度 O(n2)O(n^2)O(n2)
此題只是需要將二維dpdpdp再次優化即可
設dp[i]dp[i]dp[i]表示前綴iii的合法方案數,cnt[i]cnt[i]cnt[i]表示前綴iii中>>>的個數
dp[i]i!=∑j=0i?1[s[j]=′>′](i?j)!(?1)cnt[i?1]?cnt[j]×dp[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)cnt[i?1](-1)^{cnt[i-1]}(?1)cnt[i?1]提出來,剩余部分用NTTNTTNTT分治完成
有難度
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; }總結
以上是生活随笔為你收集整理的「LibreOJ NOI Round #2」不等关系 (dp+NTT分治)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 杀阡陌是谁演的 扮演者是马可
- 下一篇: 【BZOJ 3636】教义问答手册 (分