CF1101D GCD Counting

题目传送门

题目大意

有一颗树,每个节点有一个值,问树上最长链的长度,要求链上的每个节点之间的 \(\gcd\) 大于 \(1\)

思路

这道题其实可以用点分治来做,在这里就不讲点分治的做法了,我们直接来讲树形 dp(因为强制要求 dp 来做)。

定义状态:

首先分析一下题目。发现求出最大值唯一的要求就是每个 \(\gcd\) 都要大于 \(1\),也就是说链上的数两两之间都不能互质,所以说每两个数之间一定有相同的质因数。

看一眼数据,发现数据并不大,\(2 \times 10^5\) 的质因数个数最多也就 \(7\) 个,所以我们可以对每一个点的质因数进行预处理,\(p[i][j]\) 表示 \(i\) 的第 \(j\) 个质因数。

然后 \(f[i][j]\) 就表示以i往下都能被 \(p[i][j]\) 整除的最大链长度。

转移:

因为只是向下,所以只用考虑此节点的儿子就行了。

设x为此节点,\(v\)\(x\) 的儿子,\(xi\)\(x\) 的质因数的序号,\(vi\)\(v\) 的质因数的序号,\(ans\) 为答案

所以

\[ans=max(ans,f[x][xi]+f[v][vi]) \]

\[f[x][xi]=max(f[x][xi],f[v][vi]+1) \]

其中,更新 \(ans\) 必须在前面,因为如果先更新了 \(f[x][xi]\),那么 \(f[x][xi]\) 有可能就更新成了 \(f[v][vi]+1\),那就不是原来的长度了。

注意

  • 如果 \(a[i]\) 全部为 \(1\),则输出 \(0\)

  • 如果只有一个点,那么一个点也算是一条链

代码

#include<bits/stdc++.h>
using namespace std;
vector<int> p[200002];
int n,a[200002],f[200002][10],head[200002],cnt=0,maxx=0,ans=0;
bool ck,pd[200002],vis[200002];
struct node{
    int to,nxt;
}e[400004];
void add(int x,int y){
    e[++cnt].to=y;
    e[cnt].nxt=head[x];
    head[x]=cnt;
}
void dfs(int x){
    for(int i=head[x];i;i=e[i].nxt){
        int v=e[i].to;
        if(pd[v]) continue;
        pd[v]=1;
        dfs(v);
        if(!p[x].empty()&&!p[v].empty()){
            for(int xi=0;xi<p[x].size();xi++){
                for(int vi=0;vi<p[v].size();vi++){
                    if(p[x][xi]==p[v][vi]){
                        ans=max(ans,f[x][xi]+f[v][vi]);
                        f[x][xi]=max(f[x][xi],f[v][vi]+1);
                    }
                }
            }
        }
    }
    return;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        if(a[i]!=1) ck=1;
    }
    for(int i=1;i<=n-1;i++){
        int u,v;
        scanf("%d %d",&u,&v);
        add(u,v);
        add(v,u);
    }
    vis[1]=1;
    for(int i=2;i<=sqrt(200002);i++){
        if(!vis[i]){
            for(int j=2;j<=200002/i;j++) vis[i*j]=1;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=sqrt(a[i]);j++){
            if(a[i]%j!=0) continue;
            if(!vis[j]){
                p[i].push_back(j);
                f[i][p[i].size()-1]=1;
            }
            if(!vis[a[i]/j]&&j*j!=a[i]){
                p[i].push_back(a[i]/j);
                f[i][p[i].size()-1]=1;
            }
        }
    }
    pd[1]=1;
    dfs(1);
    if(ans) printf("%d",ans);
    else printf("%d",ck);
    return 0;
}

你觉得这道题到这里就结束了吗?

不不不!

其实,这道题有个加强版,就是把 \(n\) 的范围改成 \(1 \leq n \leq 10^6\),时间缩短成1s

这样其实只用把代码稍微改一改就行了

那么是哪里改呢?

快读,inline……(滑稽

其中最重要的一点应该是:这次如果我们还是直接暴力求质因子,就会超时了,所以我们可以先处理出所有质数,再判断 \(a[i]\) 能不能被这些质数整除就可以了。

加强版代码

#include<bits/stdc++.h>
using namespace std;
int n,a[1000001],f[1000001][10],head[1000001],pri[1000001],p[1000001][10],sum[1000001],cnt=0,pcnt=0,maxx=0,ans=0;
bool ck,pd[1000001],isprime[1000001];
struct node{
    int to,nxt;
}e[2000002];
inline int read(){
    int sum=0,f=1;
    char ch=0;
    while(!isdigit(ch)){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        sum=(sum<<1)+(sum<<3)+(ch^48);
        ch=getchar();
    }
    return sum*f;
}
inline void write(int x){
    if(x<0){
        x=-x;
        putchar('-');
    }
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline void add(int x,int y){
    e[++cnt].to=y;
    e[cnt].nxt=head[x];
    head[x]=cnt;
}
inline void dfs(int x){
    for(int i=head[x];i;i=e[i].nxt){
        int v=e[i].to;
        if(pd[v]) continue;
        pd[v]=1;
        dfs(v);
        if(sum[x]&&sum[v]){
            for(int xi=1;xi<=sum[x];xi++){
                for(int vi=1;vi<=sum[v];vi++){
                    if(p[x][xi]==p[v][vi]){
                        ans=max(ans,f[x][xi]+f[v][vi]);
                        f[x][xi]=max(f[x][xi],f[v][vi]+1);
                    }
                }
            }
        }
    }
    return;
}
int main(){
    n=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
        if(a[i]!=1) ck=1;
    }
    for(int i=1;i<=n-1;i++){
        int u,v;
        u=read();
        v=read();
        add(u,v);
        add(v,u);
    }
    memset(isprime,1,sizeof(isprime));
    for(int i=2;i<=1000001;i++){
        if(isprime[i]) pri[++pcnt]=i;
        for(int j=1;j<=cnt;j++){
            if(i*pri[j]>2000002) break;
            isprime[i*pri[j]]=0;
            if(i%pri[j]==0) break;
        }
    }
    isprime[0]=isprime[1]=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=100;j++){
            if(pri[j]>a[i]) break;
            if(a[i]%pri[j]!=0) continue;
            p[i][++sum[i]]=pri[j];
            f[i][sum[i]]=1;
           }
    }
    pd[1]=1;
    dfs(1);
    if(ans) printf("%d",ans);
    else printf("%d",ck);
    return 0;
}

不要忘记点个赞哦!

完结撒花

posted @ 2020-04-02 09:53  AFewSuns  阅读(230)  评论(2编辑  收藏  举报