LOJ #2462. 「2018 集训队互测 Day 1」完美的集合(组合数取(奇怪模数的)模 + DFS序DP+容斥)

版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/qq_35950004/article/details/90383613

题目
part1 \texttt{part1}
DP求出完美集合的价值之和,这种树上联通块的DP一般可以用DFS序DP,把DFS序拿出来从右到左,如果当前dfs序为i的点选,则可以选择dfs序为i+1的点,否则跳过整颗子树,即 f [ i ] = m a x ( f [ i + 1 ] , f [ i + s i z [ p o s [ i ] ] ] ) f[i] = max(f[i+1] , f[i + siz[pos[i]]]) 之类。
part2 \texttt{part2}
求树上的存在一种合法选点方案的方案数,发现合法选点方案构成一个联通块(或空集),那么如果一个联通块的贡献为1(有合法选点方案只算一种),可以算:所有点是合法选点的方案 - 所有边(所连接的两个点同时)是合法选点的方案。因为联通块点比边多一,只会算一遍,part1的算法可以很方便的改成统计(最大价值之和的)方案数与两个点都必须选并统计方案数。
part3 \texttt{part3}
得到集合的方案数后,还需要求选出K个集合的方案数即 ( K f [ . . ] ) \binom{K}{f[..]}
集合的方案数简单估计是 2 n 2^n 2 60 2^{60}
又有 K < = 1 0 9 K<=10^9
计算组合数不能用常规方法算。
发现模数的末尾为 5 5 ,不是质数还超过了 int \texttt{int} ,马上 Windows   +   R \texttt{Windows + R} ,键入 calc \texttt{calc} ,一试发现 mod = 5 23 \texttt{mod} =5 ^ {23}

组合数取模可以用多项式方法。
对于 m o d = 998244353 n < m o d mod = 998244353且n<mod 可以用:
i = 0 M = n ( x + i ) \prod_{i=0}^{M=\sqrt n} (x+i) x = 0 x=0 , x = M x=M …等多处多点求值即可做到 O ( M log 2 M ) O(M\log^2M) 的优秀复杂度(和巨大常数)。
对于 m o d = 5 23 n > > m o d mod = 5^{23}且 n>>mod 可以维护 n ! , k ! , ( n k ) ! n!,k!,(n-k)! 中非5的倍数的乘积 + 因子5的个数然后合并即可。
非5倍数的乘积可用多项式 f n ( x ) = 1 < = i < = n   a n d   i m o d    5 ! = 0 ( x + i ) f_n(x) =\prod_{1<=i<=n \ and \ i \mod 5 !=0} (x+i) 的常数项得到。
因为 f 10 k ( x ) = f 5 k ( x ) f 5 k ( x + 5 k ) f_{10k}(x) = f_{5k}(x)f_{5k}(x+5k) 其中后面的 f 5 k ( x + 5 k ) f_{5k}(x+5k) 的计算在 m o d    5 23 \mod 5^{23} 的意义下可以只计算前23项,剩余的都是0,又因为我们的答案只需要常数项,所以我们只需要保存多项式的前23项,暴力卷积就可以了,对于不是10的倍数的 n n 把不是 10 10 的倍数的部分的最多9个一次式乘起来就行了。
计算因子5的个数可以 O ( log n ) O(\log n)

AC Code(还是有点长):

#include<bits/stdc++.h>
#define maxn 65
#define maxm 10005
#define LL long long
#define mod 11920928955078125ll
#define pll pair<LL,LL>
using namespace std;

int n,m,w[maxn],v[maxn];
int info[maxn],Prev[maxn<<1],to[maxn<<1],cst[maxn<<1],cnt_e;
void Node(int u,int v,int c){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=c; }

inline LL mul(LL a,LL b,LL p=mod){
	return ((a*b-(LL)((long double)a*b/p+1e-10)*p)+p)%p;
}
inline LL Pow(LL base,LL k){
	LL ret=1;
	for(;k;k>>=1,base=mul(base,base))
		if(k&1)
			ret=mul(ret,base);
	return ret;
}

namespace Binom{
	#define S 23
	LL C[S][S],pw[S]={1},K;
	struct poly{
		LL a[S];
		poly(LL A=0,LL B=0){memset(a,0,sizeof a);a[0]=A,a[1]=B;}
		poly operator *(const poly &B)const{
			poly ret;
			for(int i=0;i<S;i++) if(a[i])
				for(int j=0;j+i<S;j++) if(B.a[j])
					ret.a[i+j] = (ret.a[i+j] + mul(a[i],B.a[j])) % mod;
			return ret;
		} 
		void init(LL k){
			for(int i=1;i<S;i++) pw[i] = mul(pw[i-1],k);
			static LL ar[S];
			memset(ar,0,sizeof ar);
			for(int i=0;i<S;i++)
				for(int j=0;j<=i;j++)
					ar[j] = (ar[j] + mul(a[i] , mul(pw[i-j],C[i][j]))) % mod;
			memcpy(a,ar,sizeof ar);
		}
	}P[10005];
	void Init(){
		for(int i=0;i<S;i++){
			C[i][0] = 1;	
			for(int j=1;j<=i;j++)
				C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod; 
		}
		P[0] = poly(1,0);
		for(int i=1;i<=10000;i++){
			if(i % 5)  P[i] = P[i-1] * poly(i,1);
			else P[i] = P[i-1];
		}
	}
	poly facpoly(LL n){
		if(n<=10000) return P[n];
		LL k = n/10*10;
		poly t1 = facpoly(k>>1) , t2 = t1;
		t2.init(k>>1);
		t1 = t1 * t2;
		for(LL i=k+1;i<=n;i++)
			if(i%5)	t1 = t1 * poly(i,1);
		return t1;
	}
	pll fac(LL n){
		pll ret = make_pair(facpoly(n).a[0] , n/5);
		if(n >= 5){
			pll tmp = fac(n/5);
			ret.first = mul(ret.first , tmp.first);
			ret.second = ret.second + tmp.second;
		}
		return ret;     
	}
	LL Combk(LL n){
		if(n < K) return 0;
		pll fk = fac(K),fn = fac(n) , fnk = fac(n-K);
		fn.second -= fk.second + fnk.second;
		fn.first = mul(mul(fn.first , Pow(mul(fk.first,fnk.first),mod/5*4-1)),Pow(5,fn.second));
		return fn.first;
	}
}

int dis[maxn][maxn],fa[maxn],can[maxn];
namespace DP{
	LL f[maxn][maxm] , g[maxn][maxm] , can[maxn] , Max , Up;
	int dfn[maxn] , cnt , siz[maxn];
	void dfs(int now,int ff){
		siz[now] = 1,dfn[++cnt] = now;
		for(int i=info[now];i;i=Prev[i])
			if(to[i]!=ff && can[to[i]])
				dfs(to[i],now),siz[now] += siz[to[i]];
	}
	pll ser(int x,int y){
		cnt=0;
		dfs(x,0);
		for(int i=0;i<=m;i++) f[cnt+1][i] = 0 , g[cnt+1][i] = 1;
		for(int i=cnt;i>=1;i--){
			int u = dfn[i];
			for(int j=0;j<=m;j++){
				if(u == y && j<w[u]) f[i][j] = g[i][j] = 0;
				else if(u == y || (j>=w[u] && f[i+1][j-w[u]]+v[u] > f[i+siz[u]][j])){
					f[i][j] = f[i+1][j-w[u]] + v[u];
					g[i][j] = g[i+1][j-w[u]];
				}
				else if(j<w[u] || f[i+1][j-w[u]]+v[u] < f[i+siz[u]][j]){
					f[i][j] = f[i+siz[u]][j];
					g[i][j] = g[i+siz[u]][j];
				}
				else{
					f[i][j] = f[i+siz[u]][j];
					g[i][j] = g[i+siz[u]][j] + g[i+1][j-w[u]];
				}
			}}
		return make_pair(f[1][m],g[1][m]);
	}
	LL solve(int x,int y){
		for(int i=1;i<=n;i++)	
			can[i] = ((LL)dis[x][i] * v[i] <= Max && (LL)dis[y][i] * v[i] <= Max);
		if(!can[x] || (y && !can[y])) return 0;
		pll ret =  ser(x,y);
		if(ret.first == Up)
			return Binom::Combk(ret.second);
		return 0;
	}
}

void dfs(int now,int ff,int pfa,int dist){
	dis[pfa][now] = dist , fa[now] = ff;
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff)
			dfs(to[i],now,pfa,dist+cst[i]);
}

int main(){
	scanf("%d%d%lld%lld",&n,&m,&Binom::K,&DP::Max);
	Binom::Init();
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	for(int i=1;i<=n;i++) scanf("%d",&v[i]);
	for(int i=1;i<n;i++){
		int u,v,w;scanf("%d%d%d",&u,&v,&w);
		Node(u,v,w),Node(v,u,w);
	}
	for(int i=1;i<=n;i++) DP::can[i] = 1;
	for(int i=1;i<=n;i++) DP::Up = max(DP::Up , DP::ser(i,0).first);
	if(DP::Up == 0) return puts("0"),0;
	for(int i=n;i>=1;i--) dfs(i,0,i,0);
	LL ans = 0;
	for(int i=1;i<=n;i++)
		ans = (ans + DP::solve(i,0) - (fa[i] ? DP::solve(i,fa[i]) : 0)) % mod;
	printf("%lld\n",(ans+mod)%mod);	
}

猜你喜欢

转载自blog.csdn.net/qq_35950004/article/details/90383613