周六
D. Valid Sets(树形dp)
这题2000的数据范围提示n^2的算法
树形dp一般是O(n) 所以可以考虑以每一个根分别树形dp一次
那么设当前的根为rt,rt为集合的最大值
dp[i]表示以i为根的子树的子集数,i一定选
那么每一个儿子选的话方案是dp[v],不选的话方案是1,那么遍历儿子全部乘起来就好了
还有一个问题,就是一个集合如果最大值有多个,那么在以这些最大值为根统计的时候会重复计算
那么我们就设定标号最大或者最小的那个值才可以统计答案,所以就判断一下权值相等的时候比较标号就可以了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 2e3 + 10;
const int mod = 1e9 + 7;
int a[N], n, d;
vector<int> g[N];
ll dp[N];void dfs(int u, int fa, int rt)
{ dp[u] = 1;for(int v: g[u]){if(v == fa || a[v] > a[rt] || a[rt] - a[v] > d) continue;if(a[v] == a[rt] && rt > v) continue;dfs(v, u, rt);dp[u] = (dp[u] * (dp[v] + 1)) % mod;}
}int main()
{ scanf("%d%d", &d, &n);_for(i, 1, n) scanf("%d", &a[i]);_for(i, 1, n - 1){int u, v;scanf("%d%d", &u, &v);g[u].push_back(v);g[v].push_back(u);}ll res = 0;_for(i, 1, n){dfs(i, 0, i);res = (res + dp[i]) % mod;}printf("%lld\n", res);return 0;
}
D. Cut(质因数分解+倍增)
这题很精彩,我想到了可以求以每个位置为起点最多可以多长,但是没想到用倍增加速
首先要求以每个位置为起点最远可以到哪,区间要满足两两互质
这个处理的话就是逆序遍历数组,存一下每个质数最早出现的位置,然后当前位置的值就是当前这个数可以拓展多远和下一个数得出的值取min
也就是得出从一个位置跳一次可以跳多远
那么倍增优化即可
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e5 + 10;
vector<int> ve[N];
int f[N][20], pos[N], n, q;int main()
{ scanf("%d%d", &n, &q);_for(i, 1, n) {int x; scanf("%d", &x);for(int j = 2; j * j <= x; j++)if(x % j == 0){ve[i].push_back(j);while(x % j == 0) x /= j;}if(x > 1) ve[i].push_back(x);}f[n + 1][0] = n + 1;for(int i = n; i >= 1; i--){ int cur = 1e9;for(int x: ve[i]){if(pos[x]) cur = min(cur, pos[x]);pos[x] = i;}f[i][0] = min(cur, f[i + 1][0]);}_for(j, 1, 19)_for(i, 1, n + 1)f[i][j] = f[f[i][j - 1]][j - 1];while(q--){int x, r, ans = 0;scanf("%d%d", &x, &r);for(int j = 19; j >= 0; j--)if(f[x][j] <= r){ans += 1 << j;x = f[x][j];}printf("%d\n", ans + 1);}return 0;
}