NWERC-2017 Factor-Free Tree

题目大意:给出某一颗带点权树的中序遍历序列,问是否存在一颗对应的带点权树满足条件:每个节点的子孙节点的权值都和该节点的权值互质。如果存在则输出符合条件的树的每个节点父亲节点。
这题有数个关键点,第一个是利用类似于线性筛法的算法得到每个点权值有哪些质因子,利用线性筛法可以达到均摊每个权值log(质数的数量)的效率;
第二个是对于当前序列,如果存在多个点和其他权值都互质,取其中任意一个都可以得到解,简要证明的话可以类比动态树的旋转操作,如果取点x可以得到解,则利用旋转操作,可以在保证不改变中序遍历的情况下改变树的结构得到以另一个点y
作为根节点的解,所以,不论取序列中哪一个和其他值互质的点作为当前的根节点,其结果都一样;
第三个是如何快速判断一个点和区间 [L,R] [ L , R ] 内所有点都互质。其方法O(n)预处理每个元素x左侧最近的,和其拥有公共质因数的值的位置LL[x],以及右侧最近的和其拥有公共质因数的位置RR[x],如果 LL[x]<L L L [ x ] < L RR[x]>R R R [ x ] > R 则元素x与区间 [L,R] [ L , R ] 中的所有值互质
第四个是在递归处理子序列的时候,如果直接从左到右依次判断的话,最坏会达到O(n^2)的复杂度,但是如果同时从两侧搜索则会较大地提高效率。
代码:

#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#define MAXN 1100000
#define MAXM 11100000
using namespace std;
inline void read(int &x) {
    char ch;
    bool flag = false;
    for (ch = getchar(); !isdigit(ch); ch = getchar())if (ch == '-') flag = true;
    for (x = 0; isdigit(ch); x = x * 10 + ch - '0', ch = getchar());
    x = flag ? -x : x;
}
inline void write(int x) {
    static const int maxlen = 100;
    static char s[maxlen];
    if (x < 0) {   putchar('-'); x = -x;}
    if (!x) { putchar('0'); return; }
    int len = 0; for (; x; x /= 10) s[len++] = x % 10 + '0';
    for (int i = len - 1; i >= 0; --i) putchar(s[i]);
}

int n;
int a[ MAXN ];
int su[ MAXM ],tot;
bool vis[ MAXM ];
bool IN[ MAXM ];
int num[ MAXN ][ 10 ],total[ MAXN ];
int lsh_cnt=0;
int lsh[ MAXM ];
int cnt=0;
int LL[ MAXN ], RR[ MAXN ];
int fa[ MAXN ];
int pre[ MAXM ], nex[ MAXM ];


int doit(int l,int r){
//printf("%d %d\n",l,r);
if ( l>r )
    return 0;
if ( l==r )
    return l;
int op=0;
int li=l,ri=r;
while (li<=ri)
    {
        int i;
        if ( op==0 )
            i=li++;
        else
            i=ri--;
        op=1-op;
        if ( ( LL[i]<l ) && ( RR[i]>r ) )
        {
            int tmp=doit(l,i-1);
            if ( tmp==-1 )
                return -1;
            fa[tmp]=i;
            tmp=doit(i+1,r);
            if ( tmp==-1 )
                return -1;
            fa[tmp]=i;
            return i;
        }
    }
return -1;
}



void prepare(){
for (int i=2;i<MAXM;i++)
    {
        if (!vis[i])
            su[++tot]=i;
        for (int j=1;j<=tot;j++)
            {
                if ( 1ll*su[j]*i>=MAXM )
                    break;
                vis[ su[j]*i ] = 1;
                if ( i%su[j]==0 )
                    break;
            }
    }
}


int main(){
    prepare();
    read(n);
    for (int i=1;i<=n;i++)
        {
            read(a[i]);
            if (!IN[a[i]])
            {
                IN[a[i]]=1;
                lsh_cnt++;
                lsh[a[i]]=lsh_cnt;
            }
        }
    for (int i=1;i<=tot;i++)
        {
            for (int j=su[i];j<MAXM;j+=su[i])
                if ( IN[ j ] )
                    num[ lsh[j] ][ ++total[lsh[j]] ]=i;
        }
    for (int i=1;i<=n;i++)
        a[i]=lsh[a[i]];

    for (int i=1;i<=n;i++)
    {
        int MAX=0;
        for (int j=1;j<=total[a[i]];j++)
            {
                if ( pre[num[a[i]][j]] )
                    MAX=max(MAX,pre[num[a[i]][j]]);
                pre[num[a[i]][j]]=i;
            }
        LL[i]=MAX;
    }

    for (int i=n;i>=1;i--)
    {
        int MIN=n+1;
        for (int j=1;j<=total[a[i]];j++)
            {
                if ( nex[num[a[i]][j]] )
                    MIN=min(MIN,nex[num[a[i]][j]]);
                nex[num[a[i]][j]]=i;
            }
        RR[i]=MIN;
    }

    int tmp=doit(1,n);

    if (tmp==-1)
        {
            puts("impossible");
            return 0;
        }
    for (int i=1;i<n;i++)
    {
        write(fa[i]);
        printf(" ");
    }
    printf("%d\n",fa[n]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值