【暑期多校补题记录】2025牛客多校(1)(2) 杭电多校(1)

本文发布于博客园,会跟随补题进度实时更新,若您在其他平台阅读到此文,请前往博客园获取更好的阅读体验。

跳转链接:https://www.cnblogs.com/TianTianChaoFangDe/p/18995014

开题情况

7.15牛客多校1 : 4题 - EGKL

7.17牛客多校2 : 5题 - ABFIL

7.18杭电多校1 : 4题 - 5、6、9、10

总的来说符合我们正常发挥,但每场都有瓶颈卡啊,慢慢补题查漏补缺吧。

本人个人补题情况

7.15牛客多校1 : 0题 -

7.17牛客多校2 : 1题 - G

7.18杭电多校1 : 2题 - 3、7

这周还在考试,加上有东西不会学得慢,补的有点慢T_T。

牛客多校1补题

牛客多校2补题

G - 几何,朋友

这个题,赛时思路完全正确,但对于角度的处理不当,导致被卡精度。

思路:

  • 如果点在凸多边形外,则一定是 \(2\pi\),因为最后会形成一个同心圆,而靠内的圆一定是由一个点旋转得来,因为以点为圆心做圆,和凸多边形相切的点,最多只会有一个。
  • 如果点在凸多边形内,则最后一定会形成一个圆,所以只需要考虑距离点最远的点,这些远点和旋转点形成的边,取相邻最大角度,就是答案,因为最大的被覆盖过了,则更小的角也一定会被覆盖到。

对于角度的处理,赛时选择了逆时针依次求向量夹角 + 判断方向,如果方向得到的是顺时针,则要用 \(2\pi\) 减去求得的夹角,才是真正旋转时的角度。

但这样一直过不去,大概率是被卡精度了。

赛后使用 \(atan2\) 函数一发过掉(赛时不知道这个函数T_T)。
点击查看代码(包含计算几何大模板)

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
#define double long double

const double eps = 1e-25;	// 根据题目精度要求进行修改
const double PI = acos(-1.0);	// pai, 3.1415926....

int sgn(double x) {	// 进行判断, 提高精度
    if (fabs(x) < eps) return 0;	// x == 0, 精度范围内的近似相等
    return x > 0 ? 1 : -1;			// 返回正负
}

struct Point {
    int x, y;
    Point(int x = 0, int y = 0) : x(x), y(y) {}  // 构造函数, 初始值为 0

    Point operator- (const Point &B) const { return Point(x - B.x, y - B.y); }
    
    Point operator+ (const Point &B) const { return Point(x + B.x, y + B.y); }
    
    // 向量 × 向量 (叉积)
    int operator^ (const Point &B) const { return x * B.y - y * B.x; }
    
    // 向量 · 向量 (点积)
    int operator* (const Point &B) const { return x * B.x + y * B.y; }
    
    Point operator* (const double &B) const { return Point(x * B, y * B); }
    
    Point operator/ (const double &B) const { return Point(x / B, y / B); }
    
    bool operator< (const Point &B) const { return x < B.x || (x == B.x && y < B.y); }
    
    bool operator== (const Point &B) const { return sgn(x - B.x) == 0 && sgn(y - B.y) == 0; }
    
    bool operator!= (const Point &B) const { return sgn(x - B.x) || sgn(y - B.y); }

    friend std::istream &operator>>(std::istream &is, Point &obj) {
        is >> obj.x >> obj.y;
        return is;
    }

    friend std::ostream &operator<<(std::ostream &os, const Point &obj) {
        os << obj.x << ' ' << obj.y;
        return os;
    }
};

struct Line {
    Point s, e;
    Line() {}
    Line(Point x, Point y):s(x), e(y) {}
};

using Vector = Point;

//两点间的直线距离
double dist(Point a, Point b) { return sqrt((a - b) * (a - b)); }

//向量模长
double len(Vector A) { return sqrtl(A * A); }

//单位向量
Vector norm(Vector A) { return A / len(A); }

// 判断点在直线/向量的哪一边
// 点在直线上, 返回 0 (三点共线)
// 点在直线的逆时针方向, 返回 1
// 点在直线的顺时针方向, 返回 -1
// 点 a, b (向量ab) 所在的直线和点 c
// 使用的时候要注意 a 和 b 的顺序, 有时顺序不同, 结果不同
int Cross(Point a, Point b, Point c) { return sgn((b - a) ^ (c - a)); }

// 两种分情况使用
double Cross1(Point a, Point b, Point c) { return (b - a) ^ (c - a); }

// 点 P 到直线 AB 的距离
double Dist_point_to_line(Point P, Point A, Point B) {
    Vector v1 = B - A, v2 = P - A;
    return fabs((v1 ^ v2) / len(v1));
}

// 点 P 到线段 AB 的距离
double Dist_point_to_seg(Point P, Point A, Point B) {
    if (A == B) return len(P - A);		// 如果重合, 那么就是两点的距离
    Vector v1 = B - A, v2 = P - A, v3 = P - B;
    if (sgn(v1 * v2) < 0) return len(v2);	// AP 最短
    if (sgn(v1 * v3) > 0) return len(v3);	// BP 最短
    return fabs((v1 ^ v2) / len(v1));		// 垂线
}

// 判断点是否在线段上
bool OnSegment(Point P, Point A, Point B) {
    Vector PA = A - P, PB = B - P;
    return sgn(PA ^ PB) == 0 && sgn(PA * PB) <= 0;	// <=, 包括端点; <, 不包括端点
}

//两向量的夹角
double Angle(Vector A, Vector B) {
    double t = acosl(1.0 * (A * B) / len(A) / len(B));
    return t;               // 返回 [0, π]
    //return t * (180 / PI);  // 返回 [0, 180] (角度)
}

//点到直线的映射
Point projection(Point p, Point a, Point b) {
    Vector v = b - a, u = p - a;
    return a + v * ((v * u) / (v * v));
}

//点关于直线的对称点
Point Symmetrypoint(Point p, Point a, Point b) {
    Vector v = b - a, u = p - a;
    Point q = projection(p, a, b);
    return q + (q - p);
}

// 向量 A 和要逆时针转的角度 [0, PI]
// PI / 2, 90度
Vector Rotate(Vector A, double b) {
    Vector B(sin(b), cos(b));
    return Vector(A ^ B, A * B);
}

//向量b相对于向量a是顺时针旋转还是逆时针旋转
int Vector_direction(Vector a, Vector b) {
    int cha = a ^ b;

    if(cha > 0) {
        return 0;//逆时针
    } else {
        return 1;//顺时针
    }
}

//判断三点共线
bool In_one_line(Point A, Point B, Point C) { return !sgn((B - A) ^ (C - B)); }

// 判断直线与线段是否相交
// 直线 ab 与线段 cd
bool Intersect_line_seg(Point a, Point b, Point c, Point d) {
    return Cross(a, b, c) * Cross(a, b, d) <= 0;
}

// 判断两线段是否相交(包括端点)
// 线段 ab 与线段 cd
bool Intersect_seg(Point a, Point b, Point c, Point d) {
    if (OnSegment(a, c, d) || OnSegment(b, c, d) || OnSegment(c, a, b) || OnSegment(d, a, b)) return 1;
    if (Cross(a, b, c) * Cross(a, b, d) >= 0) return 0;
    if (Cross(c, d, a) * Cross(c, d, b) >= 0) return 0;
    return 1;
}

// 求两线段距离
// 线段 ab 与线段 cd
double Dist_seg_to_seg(Point a, Point b, Point c, Point d) {
    if(Intersect_seg(a, b, c, d)) {
        return 0;
    } else {
        double ans = 1000000000;//最大值根据题目数据范围适当修改
        ans = std::min(ans, Dist_point_to_seg(a, c, d));
        ans = std::min(ans, Dist_point_to_seg(b, c, d));
        ans = std::min(ans, Dist_point_to_seg(c, a, b));
        ans = std::min(ans, Dist_point_to_seg(d, a, b));
        return ans;
    }
}

// 求两直线交点
// 首先要判断两直线是否相交, 即不平行(不重合)
// a, b 所在直线与 c, d 所在直线的交点
Point Intersection_line(Point a, Point b, Point c, Point d) {
    Vector u = b - a, v = d - c;
    double t = ((a - c) ^ v) / (v ^ u);
    return a + u * t;
}

// 判断两直线平行
// 返回true: 平行/重合, false: 相交
bool Line_parallel(Line A, Line B) { return sgn((A.s - A.e) ^ (B.s - B.e)) == 0; }

// 海伦公式求三角形面积(若题目所有信息都为整数,慎用)
double Triangle_area(Point A, Point B, Point C) {
    double a = len(A - B), b = len(A - C), c = len(B - C);
    double p = (a + b + c) / 2;
    return sqrt(p * (p - a) * (p - b) * (p - c));
}

// 叉积求三角形面积(题目信息为整数时最好是这个)
double Triangle_area2(Point A, Point B, Point C) {
    return fabs((B - A) ^ (C - A)) / 2;
}

// 当然,三角形其他公式也可以使用,比如已知两边长和夹角,则S = absinC / 2

// 求多边形面积
// 因为叉积求得的三角形面积是有向的, 在外面的面积可以正负抵消掉
// 所以能够求任意多边形面积(凸, !凸)
// p下标从 0 开始, 长度为 n
double Polygon_area(std::vector<Point> &p) {
    int n = p.size();
    double area = 0;
    for (int i = 1; i <= n - 2; i++) 
        area += (p[i] - p[0]) ^ (p[i + 1] - p[0]);
    return fabs(area / 2);  // 无向面积
	// return area / 2;        // 有向面积
}

// 鞋带定理求多边形面积
// 原理和上面相同, 不过是把原点(0, 0) 作为被指向点
// p下标从 0 开始, 长度为 n
double Polygon_area2(std::vector<Point> &p) {
    int n = p.size();
    double area = 0;
    for (int i = 0, j = n - 1; i < n; j = i++) 
        area += (p[j] ^ p[i]);
	return fabs(area / 2);  // 无向面积
    // return area / 2;        // 有向面积
}

// 判断是否是凸多边形
// 顶点必须按顺时针(或逆时针)给出, 允许共线边
// p下标从 0 开始, 长度为 n
bool Is_contex(std::vector<Point> &p) {
    int n = p.size();
    std::array<bool, 3> s{};
    for (int i = 0, j = n - 1, k = n - 2; i < n; k = j, j = i ++) {
        int cnt = sgn((p[i] - p[j]) ^ (p[k] - p[j])) + 1;
        s[cnt] = true;
        if (s[0] && s[2]) return false;
    }
    return true;
}

// 判断点在多边形的位置
// 适用于任意多边形, 不用考虑精度误差和多边形的给出顺序
// 点在多边形边上, 返回 -1
// 点在多边形内, 返回 1
// 点在多边形外, 返回 0
// p的下标从 0 开始, 长度为 n
int InPolygon(Point P, std::vector<Point> &p) {
    int n = p.size();
    bool flag = false;		// 相当于计数
    for (int i = 0, j = n - 1; i < n; j = i++) {
        Point p1 = p[i], p2 = p[j];
        if (OnSegment(P, p1, p2)) return -1;
        if (sgn(P.y - p1.y) > 0 == sgn(P.y - p2.y) > 0) continue;
        if (sgn((P.y - p1.y) * (p1.x - p2.x) / (p1.y - p2.y) + p1.x - P.x) > 0) 
            flag = !flag;
    }
    return flag;
}

// 根据方向判断点在多边形的位置
// 局限性:只能用于判断点是否在凸多边形内,并且要求点的给出顺序是顺时针(或逆时针)并且多边形点的个数>= 2
// 点在多边形边上, 返回 -1
// 点在多边形内, 返回 1
// 点在多边形外, 返回 0
// p[] 的下标从 0 开始, 长度为 n
int InPolygon1(Point P, std::vector<Point> &p, int n) {
    std::array<int, 3> st{};
    for (int i = 0, j = n - 1; i < n; j = i++) {
        st[Cross(p[j], p[i], P) + 1]++;
        if (st[0] && st[2]) return 0;
    }
    if (st[1]) return -1;
    return 1;
}

// 点集 p[] 的下标从 0 开始, 长度为 n
std::vector<Point> Andrew(std::vector<Point> &p) {
    std::vector<Point> res;
    int top = 0;
    int n = p.size();
    std::sort(p.begin(), p.end());
    for (int i = 0; i < n; i++) {  // 下凸包
        while (top > 1 && Cross1(res[top - 2], res[top - 1], p[i]) <= 0) {
            res.pop_back();
            top --;
        }
        res.push_back(p[i]);
        top ++;
    }
    int t = top;
    for (int i = n - 2; i >= 0; i--) {	// 上凸包
        while (top > t && Cross1(res[top - 2], res[top - 1], p[i]) <= 0) {
            res.pop_back();
            top --;
        }
        res.push_back(p[i]);
        top ++;
    }

    res.pop_back();  // 因为首尾都会加一次第一个点, 所以去掉最后一个
    return res;
}

void solve() {
    int n;std::cin >> n;
    std::vector<Point> a(n);

    Point p;
    std::cin >> p.x >> p.y;

    for(auto &[x, y] : a) {
        std::cin >> x >> y;
    }

    std::vector<int> d(n);

    int mx = 0;
    
    for(int i = 0;i < n;i ++) {
        d[i] = (a[i].x - p.x) * (a[i].x - p.x) + (a[i].y - p.y) * (a[i].y - p.y);
        mx = std::max(mx, d[i]);
    }

    std::vector<Point> c, e;
    for(int i = 0;i < n;i ++) {
        if(mx == d[i]) {
            c.push_back(a[i]);
        }
    }

    if(InPolygon1(p, a, n) != 0) {
        if(c.size() == 1) {
            std::cout << 2 * PI << '\n';
            return;
        }
    
        double m = 0;
    
        c.push_back(c[0]);
    
        for(int i = 0;i < c.size() - 1;i ++) {
            double tmp = atan2l(c[i].y - p.y, c[i].x - p.x);
            double tmp1 = atan2l(c[i + 1].y - p.y, c[i + 1].x - p.x);

            if(tmp1 - tmp < 0) m = std::max(m, tmp1 - tmp + 2 * PI);
            else m = std::max(m, tmp1 - tmp);
        }
    
        std::cout << m << '\n';
    } else {
        std::cout << 2 * PI << '\n';
    }
} 

signed main() {
    std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);

    std::cout << std::fixed << std::setprecision(15);

    int t = 1;std::cin >> t;
    while(t --) {
        solve();
    }

    return 0;
}

杭电多校1补题

1003 - 奸商

思路很简单,对于奇数区间,不用考虑,中间那个一定对称。

对于偶数区间,一定会包含一个更小的居中的偶数区间,因此只需要最小的偶数区间满足即可。

正解还没看,但是暴力剪枝过了,目前有两种猜测,希望有大佬可以解答一下。

  1. 杭电数据弱了
  2. 由于颜色数很少,导致如果想让某一个区间遇上匹配很慢或是根本匹配不上的话,会间接导致其它区间匹配速度加快。

均为猜测,尤其是第二条,没有严谨证明,但这个猜测是在我试图造一个卡掉暴力的数据造了半个小时发现很难造后得出的大胆的猜测。
点击查看代码

cpp 复制代码
#include <bits/stdc++.h>

void solve() {
    int n;std::cin >> n;

    std::string s;std::cin >> s;

    std::vector<int> a(17);

    for(auto & x : a) {
        std::cin >> x;
    }

    int len;std::cin >> len;
    if(len & 1)len ++;

    if(len > n) {
        std::cout << 0 << '\n';
        return;
    }

    int ans = 1e9;
    for(int k = 0;k < (1 << 17);k ++) {
        int tmp = 0;
        for(int i = 0;i < 17;i ++) {
            if(k >> i & 1) {
                tmp += a[i];
            }
        }

        if(tmp >= ans)continue;

        bool ck = true;
        for(int i = 0;i + len - 1 < n;i ++) {
            if(!ck)break;
            bool fg = false;
            for(int j = 0;i + j < i + len - 1 - j;j ++) {
                if(s[i + j] == s[i + len - 1 - j]) {
                    fg = true;
                    break;
                }

                char c = std::min(s[i + j], s[i + len - 1 - j]);
                if((k >> (c - 'a')) & 1) {
                    fg = true;
                    break;
                }
            }
            ck = fg;
        }

        if(ck) {
            ans = tmp;
        }
    }

    std::cout << ans << '\n';
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    
    int t = 1;std::cin >> t;
    while(t --) {
        solve();
    }

    return 0;
}

1007 - 树上LCM

赛时看到这个题目可高兴了,统计树上路径信息,这不妥妥的点分治板子嘛,然后,我就写了整整后半场......从TLE到WA。

这个题使用点分治理论上应该是可解的,但是难点在于,由于一个LCM可能对应很多种不同的另一个LCM,导致贡献很难算,码量巨大,还会提高代码常数,因此点分治很难解。

此题有两个解法,分别是高维前缀和和莫比乌斯反演。

解法1:高维前缀和

此解法也就是官方给出的解法。

首先,在题目数据范围内,一个数的质因子数量最多为 \(7\),因此可以考虑状压,每一位对应一个分解出的素数。

对于任何一个结点的值,我们先按照下面这个规则转化为一个状压值:

  • 如果 \(x \mod a[u] \neq 0\),那么设置为 \(-1\),因为包含这个结点的路径的 LCM 不可能是 \(x\)。
  • 如果 \(x \mod a[u] = 0\),那么按照这个规则设置状压值:对于每一个 \(x\) 分解出的素数,如果该素数在 \(a[u]\) 的次数和在 \(x\) 的次数相同,则该位设置为 \(1\),否则设置为 \(0\)。

这样做了过后,就会发现,如果对于某一个数 \(i\) 转化得到的状压值的一部分位为 \(1\),那么要让它和另一个数 \(j\) 的 LCM 为 \(x\),对于这些 \(i\) 中已经为 \(1\) 的位,\(j\) 可以为 \(1\) 也可以为 \(0\),而对于那些 \(i\) 中为 \(0\) 的位,必须要让 \(j\) 的对应位为 \(1\) 才行,也就是说,要找寻的答案,也就是某些位指定为 \(1\),其它位随意带来的贡献,但这样计算的话,复杂度必定很高,因为要去枚举不定的位的 \(01\) 情况,这时候,就是高维前缀和发挥作用的时候了。

我们可以把这些情况,使用高维前缀和,全都加到最基本的状态中,也就是对于 \(i\) 中为 \(1\) 的位,在 \(j\) 中为 \(0\),在 \(i\) 中为 \(0\) 的位,在 \(j\) 中为 \(1\),这样的话,用 \(i \oplus ((1 << k) - 1)\) 就可以得到要查询的 \(j\) 了,实现 \(O(1)\) 查询。

至于求答案,可以采用合并式的树形DP,记 \(dp[u][p]\) 为子树 \(u\) 的状压值为 \(p\) 的以 \(u\) 为起点的路径数。

假设每个结点 \(u\) 最开始只有自己一个结点,然后一棵子树一棵子树地合并上来,合并之前算一下新加的子树和当前结点 \(u\) 形成的树组合而成的贡献,然后把这棵子树合并上来,组合的过程按照上面这样计算答案贡献就行了,具体的详见代码。

这个DP方式类似于今年上半年CCPC东北四省邀请赛 - E.树上删边
点击查看代码

cpp 复制代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
    int n, x;std::cin >> n >> x;
    
    std::vector<std::vector<int>> g(n + 1);
    
    for(int i = 1;i < n;i ++) {
        int u, v;std::cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    
    std::vector<int> a(n + 1);
    
    for(int i = 1;i <= n;i ++) {
        std::cin >> a[i];
    }

    
    std::map<int, int> cnt;
    
    int tmp = x;
    for(int i = 2;i * i <= tmp;i ++) {
        if(tmp % i == 0) {
            int now = 0;
            while(tmp % i == 0) {
                now ++;
                tmp /= i;
            }
            cnt[i] = now;
        }
    }

    if(tmp != 1)cnt[tmp] = 1;
    
    auto get = [&](i64 y) -> int {
        int res = 0;
        if(std::lcm(1ll * x, y) != x) {
            return -1;
        }
        
        int th = 0;
        for(auto &[u, v] : cnt) {
            if(y % u == 0) {
                int now = 0;
                while(y % u == 0) {
                    now ++;
                    y /= u;
                }
                
                if(now == v) {
                    res |= (1 << th);
                }
            }
            
            th ++;
        }
        
        return res;
    };
    
    for(int i = 1;i <= n;i ++) {
        a[i] = get(a[i]);
    }

    const int k = cnt.size();

    std::vector dp(n + 1, std::vector<i64>(1 << k));

    i64 ans = 0;
    auto dfs = [&](auto &&self, int st, int pre) -> void {
        for(auto &v : g[st]) {
            if(v == pre)continue;
            self(self, v, st);
        }

        if(a[st] == -1)return;

        dp[st][a[st]] = 1;
        
        if(a[st] == (1 << k) - 1)ans ++;

        for(auto &v : g[st]) {
            if(v == pre)continue;

            auto suf = dp[st];
            for(int j = 0;j < k;j ++) {
                for(int i = 0;i < (1 << k);i ++) {
                    if(!(i >> j & 1)) {
                        suf[i] += suf[i | (1 << j)];
                    }
                }
            }

            for(int i = 0;i < (1 << k);i ++) {
                int now = a[st] | i;
                ans += 1ll * suf[now ^ ((1 << k) - 1)] * dp[v][i];
                dp[st][now] += dp[v][i];
            }
        }
    };

    dfs(dfs, 1, 0);

    std::cout << ans << '\n';
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    int t = 1;std::cin >> t;
    while(t --) {
        solve();
    }

    return 0;
}

解法2:莫比乌斯反演

为了补这个题专门学了莫反,然后发现......如果知道莫反的话,这不是就莫反板子题吗???

首先回顾一下莫反的公式:

如果有:

\[f(n) = \sum_{d|n} g(d) \]

那么有:

\[g(n) = \sum_{d|n}\mu(d) \times f(n/d) \]

然后回到我们这个题目,我们定义一下 \(f(n)\) 和 \(g(n)\)。

我们定义 \(f(n)\) 为:所有满足 \(LCM | n\) 的路径数。

我们定义 \(g(n)\) 为:所有满足 \(LCM = n\) 的路径数。

很显然,此题我们要求的就是 \(g(x)\),但是这并不好求,正如一开始提到的,要精确为 \(x\),情况很多,很难计算。

那 \(f(x)\) 呢?我们发现,\(f(x)\) 是很好求的,因为满足一个路径的 \(LCM | x\),只需要这条路径上面所有数字都能整除 \(x\) 即可。

再仔细观察可以发现,我们定义的 \(f(x)\) 和 \(g(x)\),不正好是满足 \(f(n) = \sum_{d|n} g(d)\) 的吗?

因此,我们可以求 \(x\) 的所有因子的 \(f(d)\),然后反演求得 \(g(x)\),完美解决。
\(f(d)\) 的求法,这里提供一个比较简便的思路,把一棵树划分成很多连通块,每一个块都满足所有数字都能整除枚举的 \(d\),对每一个块两两组合一下即可,由于任何两个点都会有唯一路径,所以答案也就是块的大小求等差数列的和,此过程可以使用树形DP简便维护,也可以提前跑一下并查集。

然后套用莫反求 \(g(x)\) 即可。

时间复杂度 \(O(n \sqrt{x})\)。
点击查看代码

cpp 复制代码
#include <bits/stdc++.h>

using i64 = long long;

const int N = 1e7 + 9;
int mu[N], notprime[N];
std::vector<int> prime;

void init() {
    notprime[0] = notprime[1] = true;
    mu[1] = 1;

    for(int i = 2;i < N;i ++) {
        if(!notprime[i])prime.push_back(i), mu[i] = -1;
        for(int j = 0;j < prime.size() && i * prime[j] < N;j ++) {
            notprime[i * prime[j]] = true;
            if(i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            }
            mu[i * prime[j]] = -mu[i];
        }
    }
} 

void solve() {
    int n, s;std::cin >> n >> s;

    std::vector<std::vector<int>> g(n + 1);
    std::vector<i64> a(n + 1);

    for(int i = 1;i < n;i ++) {
        int u, v;std::cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    for(int i = 1;i <= n;i ++) {
        std::cin >> a[i];
    }

    std::vector<int> b;

    for(int i = 1;i * i <= s;i ++) {
        if(s % i == 0) {
            b.push_back(i);
            if(s / i != i) {
                b.push_back(s / i);
            }
        }
    }

    std::sort(b.begin(), b.end());

    std::vector<i64> dp(n + 1);

    i64 ans = 0, res = 0;

    auto dfs = [&](auto &&self, int st, int pre, int x) -> void {
        dp[st] = (x % a[st] == 0);
        
        for(auto &v : g[st]) {
            if(v == pre)continue;
            self(self, v, st, x);
            dp[st] += dp[v];
        } 

        if(x % a[st] != 0)dp[st] = 0;
        if(st == 1 || x % a[pre] != 0)res += 1ll * (dp[st] + 1) * dp[st] / 2;
    };

    for(auto &x : b) {
        res = 0;
        dfs(dfs, 1, 0, x);
        ans += res * mu[s / x];
    }

    std::cout << ans << '\n';
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    init();

    int t = 1;std::cin >> t;
    while(t --) {
        solve();
    }

    return 0;
}
相关推荐
天天超方的2 个月前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(9)
做题记录
天天超方的3 个月前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(8)
做题记录
天天超方的3 个月前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(6)
做题记录
天天超方的4 个月前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(5)
做题记录
天天超方的4 个月前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(4)
做题记录
天天超方的4 个月前
【CF比赛记录】Codeforces Round 1013 (Div. 3)
cf·做题记录
天天超方的4 个月前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(2)
做题记录
天天超方的4 个月前
【CF VP记录】Codeforces Round 1008 (Div. 2)
做题记录
天天超方的4 个月前
【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
做题记录