[BZOJ3648]寝室管理(点分治+bit)

33 篇文章 0 订阅
10 篇文章 0 订阅

题目描述

传送门

题解

Sunshine学长去年的互测题orz
然而他给的solution除了点分和bit什么都没说啊。。。
硬着头皮想吧,反正我知道要用bit了。。

如果是树的话点分治+二分或者bit就能搞定
如果是环套树的话怎么办捏
首先考虑不经过环的答案,直接在外向树上点分就行了
然后考虑经过环的答案
假设当前外向树上深度为h的有x个点,那么上一棵外向树的贡献就是x*深度>=k-1-h的点的个数
假设上一个外向树深度为1,2,..的点的个数都加入到bit里,那么就直接在bit里查询就可以了
从这里可以想到用bit来维护然后查询,也就是将当前外向树之前的点都加进去,暴力枚举当前外向树的深度,然后查询
当然两棵外向树的距离是不一样的,所以在加入的过程中还需要根据距离进行相应的差分,实际上就是后一个加入的深度相比实际深度要比前一个小
还有就是因为是一个环,需要展环成链然后做n次
细节比较多。。。
时间复杂度 O(nlogn)

有一个让我挂挺了的地方,,就是点分治必须每一次更新size,否则找到的重心是不准确的

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<ctime>
using namespace std;
#define LL long long
#define N 200005
#define base 200000

int n,m,k,x,y;
int tot,point[N],nxt[N*2],v[N*2];

void addedge(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
}
namespace tree
{
    int sum,root;
    int big[N],size[N],d[N],deep[N];
    bool vis[N];
    LL ans;

    void getroot(int x,int fa)
    {
        size[x]=1;big[x]=0;
        for (int i=point[x];i;i=nxt[i])
            if (v[i]!=fa&&!vis[v[i]])
            {
                getroot(v[i],x);
                size[x]+=size[v[i]];
                big[x]=max(big[x],size[v[i]]);
            }
        big[x]=max(big[x],sum-size[x]);
        if (big[x]<big[root]) root=x;
    }
    void getdeep(int x,int fa)
    {
        deep[++deep[0]]=d[x];
        for (int i=point[x];i;i=nxt[i])
            if (v[i]!=fa&&!vis[v[i]])
            {
                d[v[i]]=d[x]+1;
                getdeep(v[i],x);
            }
    }
    int find(int l,int r,int x)
    {
        int mid,ans=deep[0]+1;
        while (l<=r)
        {
            mid=(l+r)>>1;
            if (deep[mid]+x>=k) ans=mid,r=mid-1;
            else l=mid+1;
        }
        return ans;
    }
    LL calc(int x,int now)
    {
        d[x]=now;deep[0]=0;
        getdeep(x,0);
        sort(deep+1,deep+deep[0]+1);
        LL t=0LL;
        for (int i=1;i<=deep[0];++i)
        {
            int pos=find(i+1,deep[0],deep[i]);
            t+=(LL)(deep[0]-pos+1);
        }
        return t;
    }
    void dfs(int x)
    {
        ans+=calc(x,0);
        vis[x]=1;
        for (int i=point[x];i;i=nxt[i])
            if (!vis[v[i]])
            {
                ans-=calc(v[i],1);
                sum=size[v[i]];root=0;
                getroot(v[i],0);
                dfs(root);
            }
    }
    void solve()
    {
        big[0]=N;
        sum=n;root=0;
        getroot(1,0);
        dfs(root);
        printf("%lld\n",ans);
    }
}
namespace cir_tree
{
    int sum,root;
    int h[N],father[N],big[N],size[N],d[N],deep[N];
    int c[N*2],trans[N],cnt[N],C[N*2];
    bool vis[N],flag;
    int toth,pth[N],nxth[N*2],vh[N*2],ch[N*2];
    LL ans;

    void findsizeh(int x,int fa)
    {
        size[x]=1;h[x]=h[fa]+1;
        for (int i=point[x];i;i=nxt[i])
            if (v[i]!=fa&&!vis[v[i]])
            {
                findsizeh(v[i],x);
                size[x]+=size[v[i]];
            }
    }
    void Circle(int x,int y)
    {
        memset(vis,0,sizeof(vis));
        while (x!=y)
        {
            c[++c[0]]=x;
            x=father[x];
        }
        c[++c[0]]=y;
        for (int i=1;i<=c[0];++i) vis[c[i]]=1;
        for (int i=1;i<=c[0];++i) findsizeh(c[i],0);
    }
    void findc(int x,int fa)
    {
        if (flag) return;
        vis[x]=1;father[x]=fa;
        for (int i=point[x];i&&!flag;i=nxt[i])
            if (v[i]!=fa)
            {
                if (vis[v[i]]) {Circle(x,v[i]);flag=1;return;}
                findc(v[i],x);
            }
        if (flag) return;
    }
    void getroot(int x,int fa)
    {
        size[x]=1;big[x]=0;
        for (int i=point[x];i;i=nxt[i])
            if (v[i]!=fa&&!vis[v[i]])
            {
                getroot(v[i],x);
                size[x]+=size[v[i]];
                big[x]=max(big[x],size[v[i]]);
            }
        big[x]=max(big[x],sum-size[x]);
        if (big[x]<big[root]) root=x;
    }
    void getdeep(int x,int fa)
    {
        deep[++deep[0]]=d[x];
        for (int i=point[x];i;i=nxt[i])
            if (v[i]!=fa&&!vis[v[i]])
            {
                d[v[i]]=d[x]+1;
                getdeep(v[i],x);
            }
    }
    int find(int l,int r,int x)
    {
        int mid,ans=deep[0]+1;
        while (l<=r)
        {
            mid=(l+r)>>1;
            if (deep[mid]+x>=k) ans=mid,r=mid-1;
            else l=mid+1;
        }
        return ans;
    }
    LL calc(int x,int now)
    {
        d[x]=now;deep[0]=0;
        getdeep(x,0);
        sort(deep+1,deep+deep[0]+1);
        LL t=0LL;
        for (int i=1;i<=deep[0];++i)
        {
            int pos=find(i+1,deep[0],deep[i]);
            t+=(LL)(deep[0]-pos+1);
        }
        return t;
    }
    void dfs(int x)
    {
        ans+=calc(x,0);
        vis[x]=1;
        for (int i=point[x];i;i=nxt[i])
            if (!vis[v[i]])
            {
                ans-=calc(v[i],1);
                sum=size[v[i]];root=0;
                getroot(v[i],0);
                dfs(root);
            }
    }
    void add(int loc,int val)
    {
        if (loc<=0) return;
        for (int i=loc;i<=base+base;i+=i&-i)
            C[i]+=val;
    }
    int query(int loc)
    {
        int re=0;
        if (loc<=0) return re;
        for (int i=loc;i>=1;i-=i&-i)
            re+=C[i];
        return re;
    }
    void sel(int x,int fa)
    {
        if (!cnt[h[x]]) trans[++trans[0]]=h[x];
        ++cnt[h[x]];
        size[x]=1;
        for (int i=point[x];i;i=nxt[i])
            if (v[i]!=fa&&!vis[v[i]])
            {
                sel(v[i],x);
                size[x]+=size[v[i]];
            }
    }
    void addh(int x,int y,int z)
    {
        ++toth; nxth[toth]=pth[x]; pth[x]=toth; vh[toth]=y; ch[toth]=z;
    }
    void solve()
    {
        findc(1,0);big[0]=N;
        for (int i=1;i<=c[0];++i)
        {
            vis[c[i]]=0;
            sum=size[c[i]];root=0;
            getroot(c[i],0);
            dfs(root);
            vis[c[i]]=1;
        }
        memset(vis,0,sizeof(vis));
        for (int i=1;i<=c[0];++i) vis[c[i]]=1;

        ++k;
        memset(C,0,sizeof(C));
        for (int i=1;i<=c[0];++i)
        {
            trans[0]=0;
            sel(c[i],0);
            for (int j=1;j<=trans[0];++j)
            {
                addh(c[i],trans[j],cnt[trans[j]]);
                cnt[trans[j]]=0;
            }
        }
        for (int i=1;i<=c[0];++i) c[i+c[0]]=c[i];
        for (int i=1;i<c[0];++i)
        {
            for (int j=pth[c[i]];j;j=nxth[j])
                add(vh[j]-(i-1)+base,ch[j]);
        }
        for (int i=0;i<c[0];++i)
        {
            if (i)
            {
                for (int j=pth[c[c[0]+i]];j;j=nxth[j])
                    add(vh[j]-(i-1)+base,-ch[j]);
            }
            for (int j=pth[c[c[0]+i]];j;j=nxth[j])
            {
                int qr=n-size[c[c[0]+i]]-query(k-vh[j]-(c[0]+i-1)+base);
                ans+=(LL)qr*(LL)ch[j];
            }
            for (int j=pth[c[c[0]+i]];j;j=nxth[j])
                add(vh[j]-(c[0]+i-1)+base,ch[j]);
        }
        printf("%lld\n",ans);
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);--k;
    for (int i=1;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        addedge(x,y),addedge(y,x);
    }
    if (m==n-1) tree::solve();
    else cir_tree::solve();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: bzoj作为一个计算机竞赛的在线评测系统,不仅可以提供大量的题目供程序员练习和学习,还可以帮助程序员提升算法和编程能力。为了更好地利用bzoj进行题目的学习和刷题,制定一个bzoj做题计划是非常有必要的。 首先,我们需要合理安排时间,每天留出一定的时间来做bzoj的题目。可以根据自己的时间安排,每天挑选适量的题目进行解答。可以先从难度较低的题目开始,逐渐提高难度,这样既能巩固基础知识,又能挑战自己的思维能力。 其次,要有一个计划和目标。可以规划一个每周或每月的题目数量目标,以及每个阶段要学习和掌握的算法知识。可以根据bzoj的题目分类,如动态规划、图论、贪心算法等,结合自己的实际情况,有针对性地选择题目进行学习。 此外,要充分利用bzoj提供的资源。bzoj网站上有很多高质量的题解和优秀的解题代码,可以参考和学习。还有相关的讨论区,可以与其他程序员交流和讨论,共同进步。 最后,要坚持并保持思考。做题不是单纯为了刷数量,更重要的是学会思考和总结。遇到难题时,要有耐心,多思考,多尝试不同的解法。即使不能一次性解出来,也要学会思考和分析解题过程,以及可能出现的错误和优化。 总之,bzoj做题计划的关键在于合理安排时间、制定目标、利用资源、坚持思考。通过有计划的刷题,可以提高算法和编程能力,并培养解决问题的思维习惯,在计算机竞赛中取得更好的成绩。 ### 回答2: bzoj做题计划是指在bzoj这个在线测评系统上制定一套学习和刷题的计划,并且将计划记录在excel表格中。该计划主要包括以下几个方面的内容。 首先是学习目标的设定。通过分析自己的水平和知识缺口,可以设定一个合理的目标,比如每天解决一定数量的题目或者提高特定的算法掌握程度。 其次是题目选择的策略。在excel表格中可以记录下自己选择的题目编号、题目类型和难度等信息。可以根据题目的类型和难度来安排每天的刷题计划,确保自己可以逐步提高技巧和解题能力。 然后是学习进度的记录和管理。将每天的完成情况记录在excel表格中,可以清晰地看到自己的学习进度和任务完成情况。可以使用图表等功能来对学习进度进行可视化展示,更好地管理自己的学习计划。 同时,可以在excel表格的备注栏中记录下每道题目的解题思路、关键和需要复习的知识等信息。这样可以方便自己回顾和总结,巩固所学的知识。 最后,可以将excel表格与其他相关资料进行整合,比如算法教材、题目解析和学习笔记等。这样可以形成一个完整的学习档案,方便自己进行系统的学习和复习。 总之,bzoj做题计划excel的制定和记录可以帮助我们更加有条理和高效地进行学习和刷题。通过合理安排学习目标和题目选择策略,记录学习进度和思路,并整合其他学习资料,我们可以提高自己的解题能力,并在bzoj上取得更好的成绩。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值