(树+dfs+素数筛)
题意:给定一棵n个节点的树,并且根节点的编号为p,第i个节点有属性值vali, 定义F(i): 在以i为根的子树中,属性值是vali的合约数的节点个数。y 是 x 的合约数是指 y 是合数且 y 是 x 的约数。小埃想知道对1e9+7取模后的结果.(注意:合数是指非质数的数,约数即是因子)
题解:首先如果正常遍历每个结点的子树可以得到对应的f[i]值,而对ans的贡献值为i*f[i],可是这样不断重复的遍历明显就会TLE,而且其中很多地方都重复算了。那么我们换个角度思考,当遍历到一个节点时,我们知道他对是他的倍数的根结点有f[rt](这里假定rt为他那个根)加1的贡献,即有对ans加rt。因此我们只需要从根开始遍历这棵树,每遍历到一个结点v,对它的vail值的每个合约数x的cnt[x]都加v,如果后面出现了这个因子,那么ans加上他的cnt[vail]时就得到了之前事先预处理的v值,即子结点是根结点的合约数时的贡献。不过在回溯时,要注意减去他对他所有合约数的预处理值(因为预处理的值是只有当前结点的子数能使用的贡献点)不理解的结合代码理解吧~还有由于题目数据vail的范围是1~1e4,我们可以使用素数筛预处理出每个数的所有合约数。
代码如下:
#include<iostream> #include<cstring> #include<string> #include<cstdio> #include<cmath> #include<vector> #include<queue> #include<map> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define ll long long #define mod (ll)(1e9+7) const int maxn = 2e4 + 500; vector<int>h[maxn];//合约数 bool impri[maxn];//合数 vector<int>tre[maxn]; ll cnt[maxn]; ll a[maxn]; ll ans; void fun() {//先打表得出1e4内所有数的合约数 for (int i = 2; i <= (int)1e4; i++) { //素数塞 if (!impri[i]) { for (int j = i + i; j <= (int)1e4; j += i) impri[j] = true; } } for (int i = 4; i <= (int)1e4; i++) {//打表得到合约数 if (!impri[i])continue;//素数 for (int j = i; j <= (int)1e4; j += i) h[j].push_back(i);//约数 } } void dfs(int v, int pre) { //先预处理当前结点所有合约数出现时当前节点对ans的贡献 for (int i = 0; i < h[a[v]].size(); i++) cnt[h[a[v]][i]] = (cnt[h[a[v]][i]] + v) % mod; //在当前结点对之前的祖先结点预处理的贡献值取出加到ans上 ans = (ans + cnt[a[v]]) % mod; for (int i = 0; i < tre[v].size(); i++) { if (tre[v][i] == pre)continue; dfs(tre[v][i], v); } //回溯前需要减去当前结点对后代子孙结点的贡献,因为回溯回父结点不属于其子孙 for (int i = 0; i < h[a[v]].size(); i++) cnt[h[a[v]][i]] = (cnt[h[a[v]][i]] + mod - v) % mod; } int main() { fun(); int t; cin >> t; while (t--) { int n, p, u, v; scanf("%d%d", &n, &p); for (int i = 1; i <= n; i++) tre[i].clear(); for (int i = 0; i < n - 1; i++) { scanf("%d%d", &u, &v); tre[u].push_back(v);//不能只存单向边,因为我们不清楚哪个是父结点 tre[v].push_back(u);//遍历的时候跳过父结点即可 } for (int i = 1; i <= n; i++) scanf("%lld", &a[i]); ans = 0; dfs(p, -1); printf("%lld\n", ans); } return 0; }