18.11.8 考试总结
这道题是先统计一枚个点位根节点的子树的$size$ 首先了解对于每一种连通块的$size$只有唯一一种方案
这样子先根号枚举$n$的所有因子 对于每个因子再去查看每个节点子树的$size$是否是当前因子的倍数
若是倍数 那么被他到他的父亲的这条边切割形成的两个连通块都是当期因子的倍数 那么这里肯定是分割点之一
因为多统计了一次根节点 所以这样子就相当于统计了块的个数 接下来再判断$n / size$与当前统计出的个数比较即可
代码
#include <bits/stdc++.h> using namespace std; const int N = 1e6 + 5; int tot, head[N], nex[2 * N], tov[2 * N], size[N]; int n; void add(int u, int v) { tot ++; nex[tot] = head[u]; tov[tot] = v; head[u] = tot; } void dfs(int u, int fa) { size[u] = 1; for(int i = head[u]; i; i = nex[i]) { int v = tov[i]; if(v == fa) continue; dfs(v, u); size[u] += size[v]; } } int check(int x) { int tot = 0; for(int i = 1; i <= n; i ++) tot += (size[i] % x == 0); return (tot == (n / x)); } int main( ) { freopen("count.in", "r", stdin); freopen("count.out", "w", stdout); scanf("%d", & n); for(int i = 1; i < n; i ++) { int u, v; scanf("%d%d", & u, & v); add(u, v); add(v, u); } dfs(1, 1); int ans = 0; for(int i = 2; i < n; i ++) if(!(n % i)) ans += check(i); printf("%d\n", ans + 2); }
这道题大家纷纷暴力随机化$a$了 只有我这种蒟蒻不会
这道题正解是倍增 首先肯定考虑到二分
那么考虑对于每一个二分的长度用倍增处理出从$i$点使用$2^{j}$机会(满足每次机会均小于当前二分的$len$)能跳到的最远点
因为我们不知道它到底是以谁为起点的 所以就将区间翻倍 枚举起点
对$m$二进制拆分跳倍增 在判断最后跳到的点是否跨过当前枚举的起点对应的终点即可
代码
#include <bits/stdc++.h> #define oo 3e7 using namespace std; const int N = 1e5 + 5; int dis[N][25], pos[N][25], n, m, to[N][25], a[2 * N], cmp; void Init( ) { for(int p = 1; p <= 20; p ++) for(int i = 1; i <= 2 * n; i ++) if(dis[i][p - 1] != cmp && dis[pos[i][p - 1]][p - 1] != cmp) dis[i][p] = dis[i][p - 1] + dis[pos[i][p - 1]][p - 1], pos[i][p] = pos[pos[i][p - 1]][p - 1]; } int jump(int st, int len) { int now = st; for(int p = 20; p >= 0; p --) if(len >= dis[now][p]) len -= dis[now][p], now = pos[now][p]; return now; } void prepare(int len) { for(int i = 1; i <= 2 * n; i ++) { to[i][0] = jump(i, len); } for(int p = 0; p <= 20; p ++) to[2 * n + 1][p] = 2 * n + 1; for(int p = 1; p <= 20; p ++) for(int i = 1; i <= 2 * n; i ++) to[i][p] = to[to[i][p - 1]][p - 1]; } bool check(int mid) { prepare(mid); for(int i = 1; i < n; i ++) { int now = i; for(int p = 20; p >= 0; p --) if(m & (1 << p)) now = to[now][p]; if(now >= i + n) return true; } return false; } void Solve( ) { int l = 0, r = oo, ans = oo; while(l <= r) { int mid = l + r >> 1; if(check(mid)) ans = mid, r = mid - 1; else l = mid + 1; } printf("%d", ans); } int main( ) { freopen("dinner.in", "r", stdin); freopen("dinner.out", "w", stdout); scanf("%d%d", & n, & m); memset(dis, 0x3f3f3f, sizeof(dis)); cmp = dis[0][0]; for(int i = 1; i <= n; i ++) { scanf("%d", & a[i]); a[i + n] = a[i]; dis[i][0] = a[i]; pos[i][0] = i + 1; dis[i + n][0] = a[i]; pos[i + n][0] = i + n + 1; } Init( ); Solve( ); }
第三题标程都是错的,,,
大概做法是将每个点想他能够到达的地方连边
因为不考虑$1$对方案数的贡献 所以将每两个$1$之间的边缩掉 跑一边$spfa$即可 记忆花搜索应该也是可以的
没有改这道题...。