zoukankan      html  css  js  c++  java
  • CF1554E You

    statement

    给你一个 (n) 个点的树,可以通过以下方式生成一个长度为 (n) 的序列 (a)

    • 每次在树中选取一个未被标记的节点 (u),令 (a_u) 等于与节点 (u) 相邻的未被标记的节点个数,然后将节点 (u) 标记

    对于每一个整数 (kin[1,n]),输出符合以下条件的序列 (a) 的数量模 (998244353) 的值:

    • 序列 (a) 可以由给定的树通过上述方式生成;
    • (gcd(a_1,a_2,cdots,a_n)=k)

    Hints

    (2≤n≤10^5)

    solution

    看到这种 (gcd = k) ,直接设 (f(k)) 表示答案,然后发现 (f(1)) 不好算,设 (g(k)) 表示序列 (gcd)(k) 的倍数的总方案数。

    有转移:

    [f(k) = g(k) - sum_{kcdot s le n} f(kcdot s) ]

    然后问题就变成求出一个 (g) 了。

    发现按每个点不好算,因为会影响到与这个点链接的所有点,将对象改变成每条边。

    其实影响序列 (a) 的只是由边相连的两个点,所以每条边会使得一条边所链接的其中一个点对应的 (a_i) 加一。

    所以 (g(k)) 就是求出共有多少个合法序列中,每个元素都是 (k) 的倍数。

    非常容易证明,对于两个序列 (a) ,若存在至少一条边所产生贡献的点是不一样的,且两个序列是相同的,那么与原来的图中必定有环存在。

    所以 (g(1) = 2 ^ {n - 1})

    至此,和原来树的形态没有任何关系。

    考虑 (g(k) (k ge 2)) 的情况,可以发现,在原来的树中,度数比 (k) 小的必定使得它的 (a) 变为 (0)

    归纳的想 (k = 2) 的情况,发现只有叶子节点度数小于 (2)

    再从子树到树归纳的想,若一个叶子节点是 (k) 的倍数,那么这个点与父亲连的这一条边一定会让父亲的 (a) 加一,否则自己加一,如果做不到,那么 (g(k) = 0)

    这样也说明了当 (k ge 2) 时,(g(k) = 0)(1)

    但是暴力算是 (mathcal O (n ^ 2)) 的,这样的东西一般都是根号分治,但是这道题的式子是枚举倍数的,存在重复地把一些 (f) 加上再减去,可以只枚举质数倍数的 (f) ,但这是无关紧要的。

    发现 (g(k) = 0) 的时候必须满足 (kmid (n - 1)) ,所以只要算这样的所有的 (g(k)) 即可。

    #include <bits/stdc++.h>
    #define forn(i,s,t) for(register int i=(s); i<=(t); ++i)
    #define form(i,s,t) for(register int i=(s); i>=(t); --i)
    #define rep(i,s,t) for(register int i=(s); i<(t); ++i)
    using namespace std;
    const int N = 1e5 + 3, Mod = 998244353;
    struct Mint {
    	int res;
    	Mint() {}
    	Mint(int a) : res(a) {}
    	inline friend Mint operator + (const Mint& A, const Mint& B) {
    		return Mint((A.res + B.res >= Mod) ? (A.res + B.res - Mod) : (A.res + B.res));
    	}
    	inline friend Mint operator - (const Mint& A, const Mint& B) {return A + Mint(Mod - B.res);}
    	inline friend Mint operator * (const Mint& A, const Mint& B) {return Mint(1ll * A.res * B.res %Mod);}
    	inline friend Mint& operator += (Mint& A, const Mint& B) {return A = A + B;}
    	inline friend Mint& operator -= (Mint& A, const Mint& B) {return A = A - B;}
    	inline friend Mint& operator *= (Mint& A, const Mint& B) {return A = A * B;}
    	inline friend Mint q_pow(Mint p, int k) {
    		static Mint res; res = Mint(1);
    		for(; k; k >>= 1, p *= p) (k & 1) && (res *= p, 0);
    		return res;
    	}
    };
    Mint f[N]; 
    struct Lst {
    	int dir, nxt;
    	Lst() {}
    	Lst(int _d, int _n) : dir(_d), nxt(_n) {}
    } E[N << 1];
    int G[N], cnt, n;
    inline void Add(int u, int v) {E[++cnt] = Lst(v, G[u]), G[u] = cnt;}
    int dfn[N], ord, fa[N];
    void Pre_dfs(int u) {
    	for(register int i=G[u], v; i; i=E[i].nxt) {
    		v = E[i].dir;
    		if(v == fa[u]) continue ;
    		fa[v] = u, Pre_dfs(v);
    	}
    	dfn[++ord] = u;
    }
    int w[N];
    inline bool g(int k) {
    	forn(i,1,n) w[i] = 0;
    	rep(i,1,n) {
    		int u = dfn[i];
    		if(w[u] % k) w[u] ++ ;
    		else w[fa[u]] ++ ;
    		if(w[u] % k) return 0;
    	}
    	return 1;
    }
    inline void init() {forn(i,1,n) G[i] = 0; ord = cnt = 0;}
    inline void solve() {
    	scanf("%d", &n);
    	rep(i,1,n) {
    		static int u, v;
    		scanf("%d%d", &u, &v);
    		Add(u, v), Add(v, u);
    	}
    	Pre_dfs(1);
    	form(i,n - 1,1) {
    		if((n - 1) % i) f[i] = Mint(0);
    		else if(i == 1) f[i] = q_pow(Mint(2), n - 1);
    		else f[i] = Mint(g(i));
    		for(register int j = i + i; j <= n - 1; j += i)
    			f[i] -= f[j];
    	}
    	f[n] = Mint(0);
    	forn(i,1,n) printf("%d%c", f[i].res, " 
    "[i == n]);
    }
    int T;
    int main() {
    	scanf("%d", &T);
    	while(T--) init(), solve();
    	return 0;
    }
    
  • 相关阅读:
    一道C#基础题,看你能多长时间做出来?
    终于能在这里安家了
    你知道返回多少吗?(使用Math类)
    关于implicit和explicit关键词的用法
    关于基类与派生类的学习
    js控制输入框
    Oracle 动态SQL返回单条结果和结果集 转帖
    定时器:.NET Framework类库中的Timer类比较(转帖)
    UVA10020 Minimal coverage
    UVA1388 Graveyard
  • 原文地址:https://www.cnblogs.com/Ax-Dea/p/15234189.html
Copyright © 2011-2022 走看看