BZOJ3425[POI2013]Polarization——DP+bitset+分块

题目描述

Everyone knew it would only be a matter of time. So what? Faced for years on, a peril becomes the every-day reality. It loses its meaning...

Today the letter of the Bitotian char Bittard to the Byteotian king Byteasar was released to the public. Bitotia requested annexation of the whole Byteotia on pain of using the Bit Polarizing Magnet (BPM). If used, the BPM would make each and every road in Byteotia unidirectional. The enemy knows only too well that this could be a fatal blow to the minimalist Byteotian infrastructure - there is a unique way between each pair of towns.

How badly can the BPM damage the Byteotian infrastructure? Determine the minimum and the maximum number of such pairs of towns that it will still be possible to travel from one of them to the other while observing the new roads orientation.

给定一棵树,可以对每条边定向成一个有向图,这张有向图的可达点对数为树上有路径从u到达v的点对(u,v)个数。求最小可达点对数和最大可达点对数

输入

The first line of the standard input gives a single integer N (1<=N<=250000), the number of towns in Byteotia. The N-1 lines that follow describe these roads. Each such line holds two integers, U  and V (1<=U<=V<=N) , which indicate that there is a direct road (still bidirectional at the moment) linking the towns no.  and .

输出

Two integers should be printed to the first and only line of the standard output. The first number should be the minimum and the second - the maximum number of pairs of towns which could remain connected (though in one direction only) after the roads are polarized.

样例输入

4
1 2
1 3
1 4

样例输出

3 5
 
最少可到达点数很好求,就是n-1。因为每条边的贡献最少为1,将树黑白染色,奇数层染黑色,偶数层染白色,所有黑点指向白点,答案就是n-1。
最多的怎么求?显然是一些点指向一个点,那个点再指向剩下的点(证明最后再说)。
经过中间点的答案就是两边点数的乘积,那么中间点怎么选?显然要使两边点数尽可能相等,选重心就好了。
按01背包来选择一些子树,这样做时间复杂度显然是O(n^2),我们可以用bitset优化成O(n^2/32),但显然还是过不去。
考虑到子树大小大于√n的不超过√n个,所以可以将子树大小大于√n的暴力DP,剩下的将相同大小的合并后二进制拆分来DP。这样时间复杂度就变成了O(n√n/32)
最后证明一下为什么一定是找到一个中间点最优:
首先如果改变一棵树中所有边的指向,可到达点对数不变。
如果不是中间点最优,那么一定有一条路径x指向y,x至少有两条出边(假设第二条指向a),y至少有两条入边(假设第二条入边由b指过来),改变y,b之间的边及b子树中的边或改变x,a之间的边及a子树中的边,一定有一种情况能使答案更优,这样改变下去直到找到一个中间点。
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int n;
int m;
int mn;
ll ans;
ll sum;
int x,y;
int tot;
int cnt;
int root;
int s[510];
int q[250010];
int d[250010];
int to[5000010];
int head[250010];
int next[500010];
int size[250010];
bitset<250010>f;
void add(int x,int y)
{
    tot++;
    next[tot]=head[x];
    head[x]=tot;
    to[tot]=y;
}
void dfs(int x)
{
    sum+=d[x]-1;
    size[x]=1;
    int mx=0;
    for(int i=head[x];i;i=next[i])
    {
        if(!d[to[i]])
        {
            d[to[i]]=d[x]+1;
            dfs(to[i]);
            size[x]+=size[to[i]];
            mx=max(mx,size[to[i]]);
        }
    }
    mx=max(mx,n-size[x]);
    if(mx<mn)
    {
        mn=mx;
        root=x;
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    m=sqrt(n);
    mn=n;
    d[1]=1;
    dfs(1);
    memset(d,0,sizeof(d));
    d[root]=1;
    sum=0;
    dfs(root);
    for(int i=head[root];i;i=next[i])
    {
        q[++cnt]=size[to[i]];
    }
    f[0]=1;
    for(int i=1;i<=cnt;i++)
    {
        if(q[i]<=m)
        {
            s[q[i]]++;
        }
        else
        {
            f=f|(f<<q[i]);
        }
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=s[i];s[i]-=j,j<<=1)
        {
            f=f|(f<<(j*i));
        }
        if(s[i])
        {
            f=f|(f<<(s[i]*i));
        }
    }
    for(int i=0;i<=n;i++)
    {
        if(f[i])
        {
            ans=max(ans,sum+(1ll*i*(n-i-1)));
        }   
    }
    printf("%d %lld",n-1,ans);
}
原文地址:https://www.cnblogs.com/Khada-Jhin/p/9681294.html