[codechef] TABRARRAY

TABRARRAY

题意

给定 \(n,K\),有一棵以 \(1\) 为根的 \(n\) 个点的有根树,\(i\) 的父亲是 \(f_{i}\)。你要在每个点上写一个正整数 \(a_i\),使得:

  • \(\forall 2 \leq i \leq n,a_{f_{i}}\) \(mod\) \(a_i = 0\)

  • \(\prod a_i \leq K\)

求填写正整数的方案数取模 \(998244353\)

\(n\leq 10^3,K \leq 10^{12}\)

分析

注意到每个质数都是独立的,我们将每个质数分开考虑,设 \(f_k\) 表示凑出 \(p^k\) 的方案数(其中,\(p\) 为任意质数)。

我们可以记状态 \(dp[u][s][i]\) 表示考虑以节点 \(u\) 为根的子树中质数 \(p\) 一共出现了 \(s\) 次且在当前节点出现了 \(i\) 次。我们可以以该状态加上前缀和优化写出一个复杂度为 \(O(k^3n)\)树形背包求出 \(f\) 数组(\(k = log(K)\))。

考虑如何统计方案?

注意到,除非这个质数只在 \(1\) 出现,否则必然有出现次数 \(> 1\)

所以我们可以强行让 \(a_1 = lcm(a_2,a_3……a_n)\),这样 \(\prod a_i\) 中每个质数必然至少出现两次,而这样的数是 \(powerful number\),只存在 \(\sqrt{K}\) 个,全部枚举出来,进行统计,最后再给 \(a_1\) 乘上少算的一部分贡献。

具体地,设 \(X = p_1^{c_1}\times p_2^{c_2}……p_n^{c_n}\)(所有 \(P\) 都是质数)。则其对答案的贡献即为 \(\prod f_{c_i} \times \lfloor \frac{K}{X} \rfloor\)

最后需要注意的是,如果 \(f\) 数组仍然是之前的定义,我们会算重。我们需要给 \(f\) 数组加上一个限制,即统计的是所有非根节点的 \(lcm = a_1\)。这不难以 \(O(k^3n)\) 实现,与此不再赘述。

CODE

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e3 + 10, K = 1e6 + 10, LOG_K = 41, MAX = 45, lim = 1e6, mod = 998244353;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
struct node{ int x, id, val, num; };
int n, k, ans; //全是 1 的情况 
int tem[MAX], f[MAX], g[MAX][MAX], h[MAX][MAX], dp1[N][MAX][MAX], dp2[N][MAX][MAX];
int cnt, pri[K];
bool vis[K];
vector<int> T[N];
queue<node> q;
inline void initial() //一共约八万个质数
{
	for(register int i = 2; i <= lim; i++){
		if(!vis[i]){
			pri[++cnt] = i;
			for(register int j = 1; i * j <= lim; j++) vis[i * j] = true;
		}
	}
}
inline void DFS(int u, int fa) //求 f 数组 
{
	for(register int i = 0; i < LOG_K; i++) dp1[u][i][i] = 1; //s = 0 的情况
	for(register int to : T[u]){
		if(to == fa) continue;
		DFS(to, u);
		//求 dp1 
		for(register int i = 0; i < LOG_K; i++)
			for(register int j = 0; j <= i; j++) g[i][j] = dp1[u][i][j];
			
		for(register int s = 1; s < LOG_K; s++){ //枚举以 to 为根的子树中有多少次
			//前缀和 
			memset(tem, 0, sizeof(tem));
			for(register int i = 1; i <= s; i++) tem[i] = (tem[i - 1] + dp1[to][s][i]) % mod;
			for(register int i = s + 1; i < LOG_K; i++) tem[i] = tem[i - 1];
			
			for(register int i = 1; i + s < LOG_K; i++) //枚举已经填了 
				for(register int j = 1; j <= i; j++) //枚举当前节点填 k 次
					dp1[u][i + s][j] += g[i][j] * tem[j] % mod, dp1[u][i + s][j] %= mod; 
		}
		if(u != 1) continue; //根节点特殊处理 
		
		for(register int i = 0; i < LOG_K; i++)
			for(register int j = 0; j <= i; j++) h[i][j] = dp2[u][i][j];
		for(register int s = 1; s < LOG_K; s++){ //枚举以 to 为根的子树中有多少次
			memset(tem, 0, sizeof(tem));
			for(register int i = 1; i <= min(LOG_K - 1, s); i++) tem[i] = (tem[i - 1] + dp1[to][s][i]) % mod;
			for(register int i = min(LOG_K - 1, s) + 1; i < LOG_K; i++) tem[i] = tem[i - 1];
			
			for(register int i = 1; i + s < LOG_K; i++){ //枚举已经填了
				for(register int j = 1; j <= i; j++){ //枚举当前节点填 j 次
					if(j <= s){
						dp2[u][i + s][j] = (dp2[u][i + s][j] + g[i][j] * dp1[to][s][j] % mod) % mod; 
						dp2[u][i + s][j] = (dp2[u][i + s][j] + h[i][j] * ((tem[j] - dp1[to][s][j] + mod) % mod) % mod) % mod; 
					}
					else dp2[u][i + s][j] = (dp2[u][i + s][j] + h[i][j] * tem[j] % mod) % mod;
				}
			}
		}
	}
}
inline void Sol(int pos,int now,int val)
{
	ans = (ans + (k / now) % mod * val % mod) % mod;
	for(register int i = pos; i <= cnt; i++){
		if(k / pri[i] / pri[i] < now) break;
		int S = now * pri[i] * pri[i];
		for(register int j = 2; true; j++){
			Sol(i + 1, S , val * f[j] % mod);
			if(k / pri[i] < S) break;
			S *= pri[i];
		}
	}
}
signed main()
{
	initial();
	n = read(), k = read();
	for(register int i = 2; i <= n; i++){
		int x = read();
		T[x].push_back(i), T[i].push_back(x); //建图 
	}
	DFS(1, 0);
	for(register int i = 0; i < LOG_K; i++)
		for(register int j = 0; j <= i; j++) f[i] += dp2[1][i][j], f[i] %= mod;
	Sol(1, 1, 1), printf("%lld\n", ans);
	return 0;
}
posted @ 2022-02-13 09:00  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(98)  评论(1编辑  收藏  举报