hdu4812 D Tree,平衡树,启发式合并

今天模拟2013南京赛,两个半小时做完5题开始梦游。
每次都出不了难题有木有!都是水题的场手速又拼不过有木有!

hdu4812 D Tree,南京的k题。
每个点有一个权值,问是否存在一条路径,路径所有点的权值乘积模1e6+3等于k。


这个题,大家基本都是用点分治过的,复杂度O(nlogn)。
这里介绍一个有趣的解法,也是一个我感觉很有用的思想:启发式合并。

先说说启发式合并。
启发式合并就是对于两个相同的数据结构,将点数小的数据结构的点一个个暴力插入点大的数据结构中。
如此就完成了这两个数据结构的合并。

再来说说这玩意神奇之处。
以平衡树为例,一开始有n个点,每个点有包含本身的点的平衡树。
然后将任意两点的平衡树合并,最后合并成一棵树。
如果每次都遵循小的插到大的这样的合并方式,可以证明,插入的次数不会超过O(nlogn)次。
这里简单证明一下:对于每个点,如果它从小的树拔出来插到了大的树上,那么它所在的树的大小至少会增加一倍。
树最大就是n个点,那么这个点最大的插入次数不会超过 logn 次。



这样,本题就可以用这种方式求解了。
每个叶子节点,建一个包含自己的平衡树。
非叶子节点,从子节点中选一个点数最大的,将它的平衡树作为自己的平衡树,再将其它子节点的平衡树合并到这个平衡树中。
合并的过程查找是否有匹配的值并更新答案。复杂度O(nlognlogn)。
自己生成了一些随机大数据,比点分治要稍慢一些,但是在hdu提交居然要比某些点分治更快,2000+ms。

数的逆元要在开始时预处理,不然会悲剧。
具体看代码吧:


#pragma comment(linker,"/STACK:102400000,102400000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
#define NN 101000
#define MM 1001000

map<int,int> mp[NN];
int k,n;
int te,fi[NN],ne[NN*2],v[NN*2];
int ans1,ans2,tm;
int pinv[MM];
int son[NN],tot[NN],mul[NN];
int val[NN];
int tr[NN];

const int inf = 101000000;
const int mod = 1000003;

void extend_gcd(int a,int b,int &x,int &y){
    if (b==0){
        x=1,y=0;return;
    }
    else{
        extend_gcd(b,a%b,x,y);
        int t=x;
        x=y;
        y=t-a/b*y;
        return;
    }
}
void init_inv(){
    int i;
    int x,y;
    pinv[0]=0;
    int tmod=mod;
    for(i=1;i<mod;++i){
        extend_gcd(i,tmod,x,y);
        x=(x%mod+mod)%mod;
        pinv[i]=x;
    }
}

void addedge(int a,int b){
    ne[te]=fi[a];fi[a]=te;v[te]=b;
    te++;
}

void dfs(int u,int fa){
    int e,vv,ma=-1;
    son[u]=-1;
    tot[u]=1;
    for(e=fi[u];e!=-1;e=ne[e]){
        vv=v[e];
        if (vv!=fa){
            dfs(vv,u);
            tot[u]+=tot[vv];
            if (tot[vv]>ma) {ma=tot[vv];son[u]=vv;}
        }
    }
}

void dfs2(int u,int fa){
    int e,vv;
    int tmp,tinv,a,b;
    if (son[u]==-1){
        tr[u]=++tm;mp[tm][1]=u;mul[u]=val[u];
        return;
    }
    else {
        dfs2(son[u],u);tr[u]=tr[son[u]];
        mul[u]=mul[son[u]];
        tinv=(long long)mul[u]*val[u]%mod;
        tinv=(long long)pinv[tinv]*k%mod;
        if (mp[tr[u]].find(tinv)!=mp[tr[u]].end()){
            a=mp[tr[u]][tinv];
            b=u;
            if (a>b) {int tt=a;a=b;b=tt;}
            if (a<ans1||(a==ans1&&b<ans2)) {ans1=a;ans2=b;}
        }
        vv=pinv[mul[u]];
        if ((mp[tr[u]].find(vv)!=mp[tr[u]].end()&&mp[tr[u]][vv]>u)||(mp[tr[u]].find(vv)==mp[tr[u]].end())){
            mp[tr[u]][vv]=u;
        }
    }
    for(e=fi[u];e!=-1;e=ne[e]){
        vv=v[e];
        if (vv!=fa&&vv!=son[u]){
            dfs2(vv,u);
            map<int,int>::iterator it;
            for(it=mp[tr[vv]].begin();it!=mp[tr[vv]].end();it++){
                tmp=(long long)it->first;
                tmp=(long long)tmp*mul[vv]%mod;
                tinv=(long long)tmp*mul[u]%mod*val[u]%mod;
                tinv=(long long)pinv[tinv]*k%mod;
                if (mp[tr[u]].find(tinv)!=mp[tr[u]].end()){
                    a=mp[tr[u]][tinv];
                    b=it->second;
                    if (a>b) {int tt=a;a=b;b=tt;}
                    if (a<ans1||(a==ans1&&b<ans2)) {ans1=a;ans2=b;}
                }
                tmp=(long long)tmp*pinv[mul[u]]%mod;
                if ((mp[tr[u]].find(tmp)!=mp[tr[u]].end()&&mp[tr[u]][tmp]>it->second)||(mp[tr[u]].find(tmp)==mp[tr[u]].end())){
                    mp[tr[u]][tmp]=it->second;
                }
            }
        }
    }
    mul[u]=(long long)mul[u]*val[u]%mod;
}

int main(){
    //freopen("kin2.txt","r",stdin);
    int i,a,b;
    init_inv();
    while(scanf("%d%d",&n,&k)!=EOF){
        for(i=1;i<=n;++i){
            scanf("%d",&val[i]);
            mp[i].clear();
        }
        te=0;
        memset(fi,-1,sizeof(fi));
        for(i=1;i<n;++i){
            scanf("%d%d",&a,&b);
            addedge(a,b);
            addedge(b,a);
        }
        ans1=inf;ans2=inf;
        dfs(1,-1);
        tm=0;
        dfs2(1,-1);
        if (ans1==inf){printf("No solution\n");}
        else printf("%d %d\n",ans1,ans2);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值