Codeforces 842C Ilya And The Tree
树上gcd
题目
有一棵根节点为1的树,每个结点都有权值。求每个结点从根节点到它的路径上的点的最大gcd(最多可以把一个路径上的一个点权值变成0)。
思路
关于区间gcd。。有个结论:就是可以‘暴力’,因为区间gcd收敛非常快,无论是在线段树搞,还是这次这题。都是暴力。线段树处理区间gcd的一题
开始写树形dp,dp开两个记录当前节点的路径最大gcd,dp[u][0]表示不删节点,dp[u][1]表示删除当前节点。果断WA。因为比如一条路是5,6,10,扫到6时,最大gcd是去掉5,得6,但是扫到10时,只能从6转移过来,认为区间gcd是1。说到底是dp记录的信息不够,不能获得父亲所有可能的gcd的情况。
因为区间gcd收敛很快,所以我们开n个vector,记录每个节点可能的gcd情况,暴力的转移。为了优化时间,每个节点的gcd情况要排序并去重。
由于一个数的因子个数为松散的 n√ ,而且一条路径上的gcd会很快的将到1。所以如果我们从根节点往下dfs,记录所有可以通过把1个或0个点权值变成0得到的gcd。对于每个点,答案就是转移到当前点的所有gcd的最大值。
时间复杂度为O(V∗d),其中V为顶点数,d为因子数。
代码
VS2015跑会在去重那块RE,提示越界。不知道为什么,gcc是能过的。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iostream>
#define M(a,b) memset(a,b,sizeof(a))
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int MAXN=200007;
typedef long long LL;
struct Edge
{
int to, ne;
}e[MAXN<<1];
int head[MAXN], v[MAXN], edgenum;
inline void addedge(int u, int v)
{
edgenum++;e[edgenum].to=v, e[edgenum].ne=head[u], head[u]=edgenum;
edgenum++;e[edgenum].to=u, e[edgenum].ne=head[v], head[v]=edgenum;
}
vector<int> dp[MAXN];
int fgcd[MAXN];
int gcd(int a, int b) { return b==0 ? a : gcd(b, a%b); }
void dfs(int u, int fa)
{
if(fa==-1)
{
fgcd[u]=v[u];
dp[u].push_back(0);
dp[u].push_back(v[u]);
}
else
{
fgcd[u]=gcd(fgcd[fa], v[u]);
dp[u].push_back(fgcd[fa]);
for(int i=0;i<dp[fa].size();i++)
dp[u].push_back(gcd(dp[fa][i], v[u]));
sort(dp[u].begin(), dp[u].end());
dp[u].erase(unique(dp[u].begin(), dp[u].end()), dp[u].end());
}
for(int i=head[u]; ~i;i=e[i].ne)
if(e[i].to!=fa)
dfs(e[i].to, u);
}
int main()
{
int n;
while(cin>>n)
{
for(int i=1;i<=n;i++)
scanf("%d", &v[i]);
M(head, -1);edgenum=0;M(dp, 0);
for(int i=1;i<n;i++)
{
int u, v;cin>>u>>v;
addedge(u, v);
}
for(int i=1;i<=n;i++) dp[i].clear();
dfs(1, -1);
for(int i=1;i<=n;i++)
printf("%d%c", dp[i][dp[i].size()-1], i==n ? '\n' : ' ');
}
}