自建物流的无人机实验(困难)

题目链接:http://nanti.jisuanke.com/t/440

假设知道最终答案,那么以某点x为根的点对数公式可以列出,其中Cx表示x为根的子树无人机的个数,xi为x的直接子节点:

{C_{x}\choose {2}} -  \sum\limits_{i=1}^n{C_{x_{i}}\choose {2}}

根据公式可以看出几点:

(1).某个点若满足条件,则只与以该点为根的子树无人机分布情况有关。

(2).根据(1)进一步分析知,解决某点的分配方案需要递归解决该点的所有子树

(3).对公式逆向思维,假设点x为根的树分配个数最少为Cx,那么为了满足题意,则应该使上面公式得出的值尽可能大,而Cx假设确定,即被减数固定,那么应使减数尽量小。所有Cxi的和是Cx,固定。那么,Cxi的方差越小则减数越小。

然后就能做了。

2遍DFS,第一遍确定以x为根的Cx是多少,用优先队列维护子树的Cxi,每次选取Cxi最小的i,即能保证方差最小。第二遍根据求出的Cx,确定每个点是选取还是不选取……

#include <queue>
#include <cstdio>
#include <cstring>

using namespace std;
typedef long long  ll;

#define read freopen("in.txt","r",stdin)
#define N 100020
struct str
{
    int n,v;
}ed[N];
int head[N],cnt;
void init()
{
    memset(head,-1,sizeof(head));
    cnt = 0;
}
void add(int u,int v)
{
    ed[cnt].n = head[u];
    ed[cnt].v = v;
    head[u] = cnt++;
}
ll a[N],b[N],c[N];
bool vis[N];
bool dfs(int u)
{
    b[u] = 1, c[u] = 0, vis[u] = false;
    ll t = 0;
    priority_queue< pair<int,int> >q;
    for (int i = head[u]; ~i; i = ed[i].n)
    {
        int v = ed[i].v;
        if(!dfs(v))return false;
        b[u] += b[v];
        c[u] += c[v];
        t -= c[v]*(c[v]-1)/2;
        if (c[v] < b[v])q.push(make_pair(-c[v],v));
    }
    t += c[u]*(c[u]-1)/2;
    q.push(make_pair(0,u));

    for (;c[u] <= b[u]; ++c[u])
    {
        if (t >= a[u])return true;
        t -= c[u]*(c[u]-1)/2;
        t += (c[u]+1)*c[u]/2;
        int x = -q.top().first, y = q.top().second;
        q.pop();
        t += x*(x-1)/2;
        t -= (x+1)*x/2;
        if (y == u)
        {
            vis[u] = true;
            continue;
        }
        ++ c[y];
        if (x+1 < b[y])q.push(make_pair(-x-1,y));
    }
    return false;
}
void dfs2(int u,ll f)
{
    for (int i = head[u]; ~i; i = ed[i].n)
        f -= c[ed[i].v];
    if (vis[u])--f;

    for (int i = head[u]; ~i; i = ed[i].n)
    {
        int v = ed[i].v;
        ll t = min(f,b[v]-c[v]);
        dfs2(v,t+c[v]);
        f -= t;
        c[u] -= (t+c[v]);
    }
    if (f)vis[u] = true;
}
int main()
{
    //read;
    int n;
    while (~scanf("%d",&n))
    {
        for (int i = 1; i <= n; ++i)
            scanf("%lld",a+i);
        init();
        int u,v;
        for (int i = 1; i < n; ++i)
        {
            scanf("%d%d",&u,&v);
            add(u,v);
        }
        if(!dfs(1))
        {
            puts("-1");
            continue;
        }
        dfs2(1,c[1]);
        int cnt = 0;
        for (int i = 1; i <= n; ++i)
            if(vis[i]) cnt++;
        printf("%d\n",cnt);
        bool ff = false;
        for (int i = 1; i <= n; ++i)
            if(vis[i])
            {
                if (ff)printf(" ");
                else ff = true;
                printf("%d",i);
            }
        if(cnt)puts("");
    }
    return 0;
}

这题告诉我们,学好数学很重要,列出公式就能看穿题目……

posted @ 2015-07-20 18:39  McFlurry  阅读(423)  评论(0编辑  收藏  举报