2023NOIP A层联测16 T3 货物运输

2023NOIP A层联测16 T3 货物运输

题目描述说这是一个仙人掌图,通常将问题转换为环和树的问题在使用圆方树来解决。

树解法

a i = s i − ∑ s i n a_i=s_i-\frac{\sum s_i}{n} ai=sinsi ,最终令 a i = 0 a_i=0 ai=0

通过树形 dp,从叶子节点向上转移,叶子节点要么向父亲拿资源,要么向父亲传资源,所以转移为:
a f a + = a i a n s + = w i × ∣ a i ∣ a_{fa}+=a_i\\ ans+=w_i\times|a_i| afa+=aians+=wi×ai

环解法

a i = s i − ∑ s i n a_i=s_i-\frac{\sum s_i}{n} ai=sinsi ,最终令 a i = 0 a_i=0 ai=0

不妨设编号顺序为: 1 , 2 , 3 , ⋯   , n 1,2,3,\cdots,n 1,2,3,,n

x i x_i xi 表示点 i i i i − 1 i-1 i1 运输了 x i x_i xi 个单位的资源,若 x i < 0 x_i<0 xi<0 那么是 i − 1 i-1 i1 i i i 运输,若 i = 1 i=1 i=1 那么 1 1 1 n n n 运输。

有方程组:
{ a 1 − x 1 + x 2 = 0 a 2 − x 2 + x 3 = 0 a 3 − x 3 + x 4 = 0 ⋯ a n − 1 − x n − 1 + x n a n − x n + x 1 = 0 \begin{cases} a_1-x_1+x_2=0\\ a_2-x_2+x_3=0\\ a_3-x_3+x_4=0\\ \cdots\\ a_{n-1}-x_{n-1}+x_n\\ a_n-x_n+x_1=0 \end{cases} a1x1+x2=0a2x2+x3=0a3x3+x4=0an1xn1+xnanxn+x1=0
移项化简后,我们发现有 x i = x 1 − X i x_i=x_1-X_i xi=x1Xi,若 i = 1 i=1 i=1,则 X 1 = 0 X_1=0 X1=0(化简到这里实际上只需要前 n − 1 n-1 n1 条方程),其中 X i X_i Xi 为常数。

那么答案为 ∑ w i ∣ x i ∣ = ∑ w i ∣ x 1 − X i ∣ \sum w_i|x_i|=\sum w_i|x_1-X_i| wixi=wix1Xi

现在将问题转化为求出 x 1 x_1 x1 的值,使得 ∑ w i ∣ x 1 − X i ∣ \sum w_i |x_1 - X_i| wix1Xi 最小。对于 w i = 1 w_i = 1 wi=1 的情况, x 1 x_1 x1 直接取 X i X_i Xi 的中位数。类似的,对于 w i w_i wi 没有限制的情况,可以看作有 w i w_i wi X i X_i Xi,取 ∑ w i \sum w_i wi 个数的中位数。复杂度为排序的 O ( n log ⁡ n ) O(n \log n) O(nlogn)

仙人掌

将一个环看成树上的一个点,环与环之间相邻的点看成边,依旧从叶子节点开始做。节点是环,使用环的解决方法,如果环缺少或多出值,先赊账在与父亲相连的点上(因为自己的儿子已经转移完了,多出或者少于都只可以传递或来自父亲),对于非环的边采用类似于树形 d p dp dp 的解决方法。

实现

可以先建出圆方树,一个方点对应一个环或者一条边,使用方点转移即可。

#include<bits/stdc++.h>
using namespace std;

#define int long long

const int maxn=1e6+5,maxm=1e6+5;

struct node
{
    int to,nxt;
}edge[maxn*4],tred[maxm];

int n,m,trtot,tot,tp,cok,tx,pj,ans;
int head[maxn],trhead[maxm],s[maxn],dfn[maxn],low[maxn],stk[maxn],sz[maxn],f[maxn];

vector<int>vec[maxm];

map<pair<int,int>,int>mp;

void add(int &tot,int head[],node edge[],int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].nxt=head[x];
    head[x]=tot;
}

void tarjin(int u)//建圆方树
{
    dfn[u]=low[u]=++cok;
    stk[++tp]=u;
    for(int i=head[u];i;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(!dfn[v])
        {
            tarjin(v);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u])
            {
                add(trtot,trhead,tred,++tx,u);
                add(trtot,trhead,tred,u,tx);
                vec[tx].push_back(u);
                int x=0;
                do
                {
                    x=stk[tp--];
                    add(trtot,trhead,tred,tx,x);
                    add(trtot,trhead,tred,x,tx);
                    vec[tx].push_back(x);
                }while(x!=v);
            }
        }
        else low[u]=min(low[u],dfn[v]);
    }
}

void dfs2(int u)//遍历圆方树求答案
{
    int sum=0;
    for(int i=trhead[u];i;i=tred[i].nxt)
    {
        int v=tred[i].to;
        if(v==f[u]) continue;
        dfs2(v);
        if(u>n) sum+=s[v];
    }
    if(u<=n) return ;

    if(vec[u].size()==2)//非环边直接转移
    {
        int son;
        if(vec[u][0]==f[u]) son=vec[u][1];
        else son=vec[u][0];
        s[f[u]]+=s[son];
        ans+=abs(s[son])*mp[make_pair(f[u],son)];
        return ;
    }

    priority_queue< pair<int,int>,vector< pair<int,int> >, greater< pair<int,int> > >que;//环边
    while(!que.empty()) que.pop();
    int cnt=vec[u].size();
    sum+=s[f[u]];
    int tmp=s[f[u]];
    s[f[u]]=s[f[u]]-sum;
    que.push(make_pair(0,mp[ make_pair(vec[u][0],vec[u][cnt-1]) ]));
    int ct=0,sg=mp[ make_pair(vec[u][0],vec[u][cnt-1]) ];
    for(int i=1;i<cnt;i++)
    {
        int w=mp[ make_pair(vec[u][i],vec[u][i-1]) ];
        ct+=s[ vec[u][i-1] ];
        que.push(make_pair(ct,w)),sg+=w;
    }
    ct=0;
    int x=0;
    while(!que.empty())//中位数
    {
        ct+=que.top().second;
        if(ct>sg/2) {x=que.top().first;break;}
        que.pop();
    }
    ct=0;
    ans+=abs(ct-x)*mp[make_pair(vec[u][0],vec[u][cnt-1])];
    for(int i=1;i<cnt;i++)
    {
        int w=mp[ make_pair(vec[u][i],vec[u][i-1]) ];
        ct+=s[ vec[u][i-1] ];
        ans+=w*abs(x-ct);
    }
    s[f[u]]=sum;
}
void dfs1(int u,int fa)
{
    f[u]=fa;
    for(int i=trhead[u];i;i=tred[i].nxt)
    {
        int v=tred[i].to;
        if(v==fa) continue;
        dfs1(v,u);
    }
}

signed main()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++) scanf("%lld",&s[i]),pj+=s[i];
    pj/=n;
    for(int i=1;i<=n;i++) s[i]=s[i]-pj;
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%lld%lld%lld",&x,&y,&z);
        add(tot,head,edge,x,y);
        add(tot,head,edge,y,x);
        mp[make_pair(x,y)]=z;
        mp[make_pair(y,x)]=z;
    }
    tx=n;

    tarjin(1);
    dfs1(n,0);//答案是在计算过程中统计的,所以出发点设为圆点更方便统计
    dfs2(n);

    printf("%lld",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值