BZOJ 1999 [Noip2007]树网的核(2282 [Sdoi2011]消防) - 树的直径+单调队列

首先贴出一篇我认为讲的最好的:
http://blog.csdn.net/vmurder/article/details/44627469

首先证明结论:
证明一:树的核必在直径上
1.选定的核与直径无交集
显然选的核在直径的一个分支上,如图,肯定不如核与直径相接的那段直径优
BZOJ 1999 [Noip2007]树网的核(2282 [Sdoi2011]消防) - 树的直径+单调队列_第1张图片
2.选定的核与直径有一部分交集
BZOJ 1999 [Noip2007]树网的核(2282 [Sdoi2011]消防) - 树的直径+单调队列_第2张图片
如图若选红色部分为核,那么不在直径的一部分相当于优化了BC段的长度,然而如果AD为直径是条件,那么BC必然不比BD长,那么相当于最远的距离没有改变,肯定不如全在直径上优
3.排除以上两种情况,确定选定的核应当在直径上

证明二:任意一条直径均可,不必特定直径
设选定的核d在直径L1上,另有一条直径L2,易证L1与L2一定有交集(反证一下就就好)
1.d与L2无交集
BZOJ 1999 [Noip2007]树网的核(2282 [Sdoi2011]消防) - 树的直径+单调队列_第3张图片
易看出红色部分很明显比绿色部分劣

2.d与L2有部分交集
BZOJ 1999 [Noip2007]树网的核(2282 [Sdoi2011]消防) - 树的直径+单调队列_第4张图片
红色部分优化掉了DE,而由于如图所示两条直线均为直径,所以由反证法有DE=DF,于是实际上优化掉DE,而DF依旧没有改变,是无效的,不如全部放在CD上优(为啥不放在EF上呢。。。如证明一所示)

3.排除掉两种情况,树的核必然优先在直径相交处

对于这道题应该怎么找直径相交呢,枚举?肯定不现实,而且还得讨论,于是在一条直径上找,必然包含直径相交的情况,然后观察在直径上的性质,发现对于一段核,发生贡献的总共就两个地方:一个是核两端点到直径两端点的距离,另一个是核上伸出的分枝到核的距离BZOJ 1999 [Noip2007]树网的核(2282 [Sdoi2011]消防) - 树的直径+单调队列_第5张图片
然后固定一个右端点,肯定选择核的部分越大越优,于是左端点应该尽力向左,易知随着右端点向右,左端点的位置肯定单调向前(意会一下不用较真),第一步贡献好求,第二步贡献则须考虑到记录伸出分枝的最长深度。每次左端分枝数减小,右端加一。根据单调队列的性质,需要满足两个单调,一个是位置单调,一个是值单调。对于两个同时在队列的值v1比v2先进队且v1 < v2,则v1比v2先出队,有v2保证,v1删去不会造成影响,这是单调队列的定义。而对于这道题每次右节点右移,左节点相应变化后维护一个分枝深度的单调递减值,而进队时间单调递增的队列即可,每回取队首即为最大值。

求树的直径用dfs可能会爆栈,不如用bfs好了。。。

#include
#include
#include
#include
#include
#include

using namespace std;

const int maxn=500005;

struct edge
{
    int to,next,val;
}e[maxn<<1];

int n,cnt,xx,yy,length,S,ans=0x3f3f3f3f;
int head[maxn],depth[maxn],len[maxn],pere[maxn],val[maxn],sum[maxn],pos[maxn];
bool vst[maxn];

void insert(int a,int b,int c)
{
    e[++cnt].to=b;e[cnt].val=c;e[cnt].next=head[a];head[a]=cnt;
}
void bfs(int s,int &o)
{
    memset(depth,0,sizeof depth);
    memset(pere,0,sizeof pere);
    memset(val,0,sizeof val);
    queue<int>q;
    q.push(s);
    o=s;depth[s]=1;
    pere[s]=-1;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=e[i].next)if(e[i].to!=pere[x])
        {
            int y=e[i].to;
            pere[y]=x;
            depth[y]=depth[x]+e[i].val;
            val[y]=e[i].val;
            if(depth[y]>depth[o])o=y;
            q.push(y);
        }
    }
}
void diameter()
{
    bfs(1,xx);
    bfs(xx,yy);
}
void dfs(int x,int &key)
{
    vst[x]=true;
    for(int i=head[x];i;i=e[i].next)if(!vst[e[i].to]&&e[i].to!=pere[x])
    {
        depth[e[i].to]=depth[x]+e[i].val;
        if(depth[e[i].to]>key)key=depth[e[i].to];
        dfs(e[i].to,key);
    }
}
void traversal()
{
    length=0;
    memset(depth,0,sizeof depth);
    for(int i=yy;i!=-1;i=pere[i])
        pos[++length]=i,
        sum[length]=sum[length-1]+val[pos[length-1]],
        dfs(i,len[length]);
}
int q[maxn];
void solve()
{
    int head=1,rear=0,left=1;//
    for(int i=1;i<=length;i++)
    {
        while(head<=rear&&sum[i]-sum[left]>S)left++;
        while(head<=rear&&left>head)head++;
        while(head<=rear&&len[q[rear]]<=len[i])rear--;
        q[++rear]=i;
        ans=min(ans,max(len[q[head]],max(sum[left],sum[length]-sum[i])));
    }
    cout<int main()
{
    scanf("%d%d",&n,&S);
    for(int i=1,u,v,val;i"%d%d%d",&u,&v,&val),
        insert(u,v,val),
        insert(v,u,val);
    diameter();
    traversal();
    solve();
    return 0;
}

你可能感兴趣的:(其他图论相关,单调队列,图论证明)