• CF1034C Region Separation


    CF1034C Region Separation

    题目大意

    题目链接

    给定一棵 (n) 个点的树。每个点有权值,记为 (a_{1dots n})

    你想砍树。你可以砍任意轮,每轮你选择一些边(至少一条)断开,需要满足每轮结束后每个连通块的权值和是相等的。

    求有多少种砍树方案。两种方案不同,当且仅当轮数不同或者某一轮砍的边不同。答案对 (10^9 + 7) 取模。

    数据范围:(1leq nleq 10^6)(1leq a_ileq 10^9)

    本题题解

    取任意点为根(不妨取 (1)),把树变成有根树。设 (S) 为所有节点的点权和,(s_i) 表示以 (i) 为根的子树内的点权和。

    考虑只进行一轮划分,有多少种方案。设把树划分为了 (k) 块,那么存在方案的一个必要条件是:(k)(S) 的约数。此时每块内的点权和是 (frac{S}{k})

    我们从叶子开始,类似树形 DP 的过程,每当当前连通块的和达到 (frac{S}{k}),就把当前连通块割下来。具体来说,对所有 (i = 2dots n),如果 (s_{i} mod frac{S}{k} = 0),就切断 (i)(mathrm{fa}(i)) 之间的边。容易发现,如果有解,我们这样会求出唯一的一组解如果无解,按此方法最终得到的连通块数会小于 (k)

    通过如上讨论,我们已经可以回答【只进行一轮划分,把树划分为 (k) 块的方案数】,设为 (f(k)),则:

    [f(k) = egin{cases} 0 && Smod k eq 0\ displaystyle left[left(sum_{i = 1}^{n} left[s_i mod frac{S}{k} = 0 ight] ight) = k ight]&& ext{otherwise} end{cases} ]

    也就是说,(f(k)) 只能等于 (0)(1),并且它为 (1) 当且仅当:(k)(S) 的约数,且恰有 (k)(i) 满足 (s_i mod frac{S}{k} = 0)

    暴力求出 (f(1)dots f(n)) 的时间复杂度是 (mathcal{O}(n^2)) 的,我们想要更快。

    考虑 (s_i mod frac{S}{k} = 0) 这个要求。转化一下:

    [egin{align} &s_i = tcdot frac{S}{k}\ Arr & s_i cdot k = t cdot S\ Arr & S mid s_i cdot k\ Arr & frac{S}{gcd(S, s_i)}mid k end{align} ]

    也就是说,这个要求等价于 (k)(frac{S}{gcd(S, s_i)}) 的倍数。

    那么对每个 (s_i),设 (v = frac{S}{gcd(S, s_i)}),它可以对 $k = v, 2v, 3v, dots $ 产生贡献,另外注意 (k leq n)。于是我们用桶存一下,然后累加一遍,就能在调和级数的时间复杂度内,求出所有 (f(k)) 了。时间复杂度 (mathcal{O}(nlog n))

    然后考虑不止划分一轮的问题。设 (mathrm{dp}(k)) 表示划分了若干轮,得到 (k) 个连通块的方案数。根据划分方案的唯一性,它上一轮划分出的连通块数,能且仅能是 (k) 的约数。于是可以写出转移:

    [mathrm{dp}(k) = f(k)cdot sum_{ i | k } mathrm{dp}(i) ]

    答案就是 (sum_{k = 1}^{n} mathrm{dp}(k))。这个 DP 的时间复杂度也是调和级数: (mathcal{O}(nlog n)) 的。

    时间复杂度 (mathcal{O}(n(log n + log a))),其中 (log a) 来自求 (gcd)

    参考代码

    片段:

    const int MAXN = 1e6;
    const int MOD = 1e9 + 7;
    
    int n, a[MAXN + 5], fa[MAXN + 5];
    ll s[MAXN + 5], S;
    int f[MAXN + 5], dp[MAXN + 5];
    
    ll gcd(ll x, ll y) { return (!y) ? x : gcd(y, x % y); }
    
    int main() {
    	cin >> n;
    	for (int i = 1; i <= n; ++i) {
    		cin >> a[i];
    		s[i] = a[i];
    		S += a[i];
    	}
    	for (int i = 2; i <= n; ++i) {
    		cin >> fa[i];
    	}
    	for (int i = n; i >= 2; --i) {
    		s[fa[i]] += s[i];
    	}
    	assert(s[1] == S);
    	
    	for (int i = 1; i <= n; ++i) {
    		ll v = S / gcd(S, s[i]);
    		if (v <= n) {
    			f[v]++;
    		}
    	}
    	for (int i = n; i >= 1; --i) {
    		for (int j = i + i; j <= n; j += i) {
    			f[j] += f[i];
    		}
    	}
    	
    	for (int i = 1; i <= n; ++i) {
    		f[i] = (f[i] == i); // 恰好能划分为 i 块
    	}
    	
    	dp[1] = 1;
    	int ans = 0;
    	for (int i = 1; i <= n; ++i) {
    		if (!f[i]) {
    			dp[i] = 0;
    			continue;
    		}
    		for (int j = i + i; j <= n; j += i) {
    			dp[j] = (dp[j] + dp[i]) % MOD;
    		}
    		ans = (ans + dp[i]) % MOD;
    	}
    	
    	cout << ans << endl;
    	return 0;
    }
    
  • 相关阅读:
    首发Zend Studio 9.0.2正式版注册破解
    PHP 中Cookie和Session的使用
    PHP设计模式职责和单例模式
    软件工程工具学习(1)Visio
    java复习(4)数字处理类
    java复习(1)java与C++区别
    微信开发(1)微信防撤回功能实现
    java复习(3)字符串、数组
    java复习(2)java基础杂记
    java复习(5)接口、继承、多态
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14402312.html
Copyright © 2020-2023  润新知