HDU 4812 D Tree 树的点分治

【题目大意】

先有一棵树,点i有点权vi,你的任务是找一条简单链,使得链上所有点的点权乘积模上10^6+3 = k。输出这条链的两个端点(u,v),如果有多条,输出字典序最小的。


【思路】

很明显能想到点的分治,现在的关键是,如果根为root,怎么去找不同的子树里的两个点u,v,使得u到v,路径上点的乘积满足题意。我们可以假设u到root,路径上的乘积为x,v到root(不包括root),路径上的乘积为y。如果(u,v)满足题意的路径,那么有 x*y%MOD == k。整理一下,实际上就是 x*y + MOD*t == k,其中,t为一个整数。注意到MOD是一个质数,由扩展gcd知识得:对于确定x,y在[0,MOD)这个区间,只有一个解。当然,如果x == 0的话,y是不止一个解了,但是题目的数据范围,显然x不可能为0。至此,我们感觉已经可以解了,我们分治的时候,直接找对应的y就是了。

但是悲剧的是,此题时间很紧,每次都用扩展gcd去找对应解要超时,对于确定k,先预处理也要超时。我们考虑另外一种解法,因为 y == ( (k - MOD*t)/ x )%MOD,我们可以只预处理一次,将所有x的逆元求出来,那么 y == k*x' %MOD。这样,这题就可以做了。


#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<cctype>
#include<string>
#include<algorithm>
#include<iostream>
#include<ctime>
#include<map>
#include<set>
using namespace std;
#define MP(x,y) make_pair((x),(y))
#define PB(x) push_back(x)
typedef __int64 LL;
//typedef unsigned __int64 ULL;
/* ****************** */
const int INF=100011122;
const double INFF=1e100;
const double eps=1e-8;
const LL mod=1000000007;
const int NN=100010;
const int MM=1000010;
/* ****************** */

const LL MOD=1000003;
int inv[MOD];
int re[MOD];
struct G
{
    int v,next;
}E[NN*2];
int p[NN],T;
int val[NN];
int si[NN],m_si[NN],tol;
bool vis[NN];
int a[NN],mul[NN],b[NN];
pair<int,int> ans;


LL exgcd(LL a,LL &x,LL b,LL &y)
{
    if(b==0)
    {
        x = 1;
        y = 0;
        return a;
    }
    LL d = exgcd(b,x,a%b,y);
    x = y;
    y = (d - a*x)/b;
    return d;
}
void init(int k)
{
    int i;
    LL x0,t;
    for(i=1;i<MOD;i++)
    {
        exgcd(i,x0,MOD,t);
        x0*=k;
        x0%=MOD;
        if(x0<0)x0+=MOD;
        inv[i]=x0;
    }
}

void add(int u,int v)
{
    E[T].v=v;
    E[T].next=p[u];
    p[u]=T++;
}

void dfs_fr(int u,int fa,LL temp)
{
    int i,v;
    LL tt = temp*val[u]%MOD;
    si[u]=1;
    m_si[u]=0;
    a[++tol]=u;
    mul[tol]=tt;
    for(i=p[u];i+1;i=E[i].next)
    {
        v=E[i].v;
        if(v==fa || vis[v])continue;
        dfs_fr(v,u,tt);
        si[u]+=si[v];
        m_si[u]=max(m_si[u],si[v]);
    }
}

void goo(int u,int v)
{
    if(u>v)swap(u,v);
    pair<int,int>temp = MP(u,v);
    ans = min(ans,temp);
}

void solve(int root,int kk)
{
    int i,j,v,m,t,dui,toll=0;
    int new_root=root;

    tol=0;
    dfs_fr(root,-1,1);
    m=m_si[root];
    for(i=1;i<=tol;i++)
    {
        t = max(tol-si[a[i]],m_si[a[i]]);
        if(m>t)
        {
            m=t;
            new_root = a[i];
        }
    }

    for(i=p[new_root];i+1;i=E[i].next)
    {
        v=E[i].v;
        if(vis[v])continue;
        tol=0;
        dfs_fr(v,new_root,1);
        for(j=1;j<=tol;j++)
        {
            if((LL)mul[j]*val[new_root]%MOD==kk)
            {
                goo(new_root,a[j]);
            }
            dui = (LL)inv[ mul[j] ]*kk%MOD;
            t = re[ dui ];
            if( t != INF)
            {
                goo(t,a[j]);
            }
        }

        for(j=1;j<=tol;j++)
        {
            t = (LL)mul[j]*val[new_root]%MOD;
            re[t] = min(re[t], a[j]);
            b[toll++] = t;
        }
    }

    vis[new_root]=true;
    for(j=0;j<toll;j++)
        re[ b[j] ]=INF;
    for(i=p[new_root];i+1;i=E[i].next)
    {
        v=E[i].v;
        if(vis[v])continue;
        solve(v,kk);
    }
}

inline int read()
{
    char ch;
    int d, flag = 1;

    while( ch= getchar(), ch== ' ' || ch== '\n' );

    if( ch== '-' ) { flag= -1; d= 0; }
    else{ d= ch- '0'; }

    while( ch= getchar(), ch>= '0' && ch<= '9' )
        d= (d<<1) + (d<<3) + ch- '0';

    return d*flag;
}

int main()
{
    int n,k,i,u,v;

    init(1);
    for(i=0;i<MOD;i++)
        re[i]=INF;

    while(scanf("%d%d",&n,&k)!=EOF)
    {
        memset(p,-1,sizeof(p));
        T=0;

        for(i=1;i<=n;i++)
        {
            val[i] = read();
        }
        for(i=1;i<n;i++)
        {
            u = read();
            v = read();
            add(u,v);
            add(v,u);
        }

        memset(vis,false,sizeof(vis));
        ans = MP(INF,INF);
        solve(1,k);

        if(ans.first==INF)
            puts("No solution");
        else
        {
          //  cout<<"ans==";
            printf("%d %d\n",ans.first,ans.second);
        }

    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值