浅谈点分治(JZOJ4715)

前不久搞了搞点分治,一直没有打模板练习,昨晚练习了一下(POJ1741),因为一个初始值赋成0x3f调了半小时,事实告诉我们,随手赋值0x3f3f3f3f

谈一下点分治是啥吧,点分治呢,其实个人感觉是对于求解树上路径问题的暴力优化:
对于一棵无根树,求解关于它上面路径的权值等问题均可以用点分治解决,但是需要有很好的理解及代码,分析能力。
点分治:
对于一棵无根树首先找到一个点作为它的根,因为随便拿一个做根求解复杂度过于高,近似(N^2)的复杂度,于是我们考虑一棵树的重心(重心指将这个点删去后,剩下的最大联通块包含的节点数最小),知道树的重心分割后的联通块最大节点数不超过N/2,于是我们可以用重心作为根,对于每次分割的联通块为一层,则最多有logN层,每一层查找复杂度为O(N),则总体复杂度近似O(NlogN),但是常数比较大,所以我认为是优化的暴力
找到根之后,我们对于根统计ans,我们知道对于一个点,只有两条路径,通过它的,不通过它的,由于分治,我们只要求计算通过它的,很容易知道通过根的肯定在两个不同的子树,但要排除在同一子树的情况(也就是不通过它的),我们可以先求出所有deep,然后求出根的ans,再对于它的子树求deep,然后减去他们对答案的贡献就行了,差不多就这样ans(root)-Σans(tree).
然后对于每一个子树求解下一层的根,继续进行点分治就OK了,那么例题拿一下JZOJ4715讲

题面:

给出一棵树,求出最小的k,使得,且在树中存在路径p,使得k>=S且k<=E。(k为路径p上的边的权值和)

输入
第一行给出N,S,E。N代表树的点数,S,E如题目描述。
下面N-1行给出这棵树的相邻两个节点的边及其权值W。
对于20%的数据满足n<=300
对于50%的数据满足n<=3000
对于60%的数据满足n<=10^5
对于以上数据,满足|E-S|<=50
对于100%的数据满足n<=10^5,|E-S|<=10^6
对于所有数据满足1<=Wi<=1000,|E|,|S|<=10^9

输出
输出共一行一个整数,表示答案。若无解输出-1。

样例输入 输出
5 10 40 16
2 4 80
2 3 57
1 2 16
2 5 49

那么对于题目进行剖析,关于树上路径权值,且是无根树,那么可以点分治解决,分析一下,要求我们找出树上一条路径的权值之和>=S 且 <=E 且权值最小,求最小,而且存在无解情况,由于最小,我们可以忽略上边界,以下边界为判定界限,然后就是点分治,比较模板了,对于每个重心的子树dfs,然后求出deep,排序后,l,r从两边开始跳,由于我们不能求在同一子树内的答案,于是在求解deep的时候我们记录一下每个点的属于哪个子树,如果所属子树相同,我们知道对于处于同一子树的,我们保证ans要小,所以判断一下它和r-1的距离是不是

# include<cstdio>
# include<algorithm>
# include<cstring>
using namespace std;
const int N = 2e5+10;
typedef long long ll;
struct b
{
    int nex,to;
    ll dis;
}e[N*2];
struct c
{
    int f;
    ll di;
}dep[N];
int n,root,i,tot,len,sum,s,E;
ll dis,d[N],ans;
int maxx[N],siz[N],st[N];
bool vis[N];
int cmp(c x,c y)
{
    return x.di < y.di;
}
void add(int u,int v,int x)
{
    e[++tot].to = v;
    e[tot].nex = st[u];
    st[u] = tot;
    e[tot].dis = x;
}
void getrt(int x,int fa)
{
    siz[x] = 1,maxx[x] = 0;
    for (int i = st[x];i;i = e[i].nex)
    {
        int v = e[i].to;
        if (v == fa || vis[v]) continue;
        getrt(v,x);
        siz[x] += siz[v];
        maxx[x] = max(maxx[x],siz[v]);
    }
    maxx[x] = max(maxx[x],sum - siz[x]);
    if (maxx[x] < maxx[root]) root = x;
}
void getdeep(int x,int fa,int zs)
{
    dep[++len].di = d[x];
    dep[len].f = zs;
    for (int i = st[x];i;i = e[i].nex)
    {
        int v = e[i].to;
        if (v == fa || vis[v]) continue;
        d[v] = d[x] + e[i].dis;
        if (!zs) getdeep(v,x,v);
        else getdeep(v,x,zs);
    }
}
ll cal(int x,int di)
{
    len = 0,d[x] = di;
    getdeep(x,0,0);
    sort(dep + 1,dep + len + 1,cmp);
    int l = 1,r = len; ll last = 0x3f3f3f3f;
    while (l < r)
    {
        if (dep[l].f == dep[r].f)
        {
            if (dep[l].di + dep[r - 1].di < s) l++;
            else r--;
        }else
        {
            if (dep[l].di + dep[r].di >= s) { last = min(last,dep[l].di + dep[r].di); r--; }
            else l++;
        }
    }
    return last;
}
void solve(int x)
{
    ans = min(ans,cal(x,0));
    vis[x] = 1;
    for (int i = st[x];i;i = e[i].nex)
    {
        int v = e[i].to;
        if (vis[v]) continue;
        sum = siz[v];
        root = 0;
        getrt(v,0);
        solve(root);
    }
}
int main()
{
    scanf("%d%d%d",&n,&s,&E);
    ans = 0x3f3f3f;
    memset(vis,0,sizeof(vis));
    memset(st,0,sizeof(st));
    sum = n;
    for (i = 1;i < n; i++)
    {
        int u,v,x;
        scanf("%d%d%d",&u,&v,&x);
        add(u,v,x),add(v,u,x);
    }
    maxx[0] = 0x3f3f3f3f;
    getrt(1,0);
    solve(root);
    printf("%lld\n",ans > E ? -1 : ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值