COCI2016/2017 Round1T4 Mag

文章目录

题目

题目1题目2

分析

既然是乘积,容易想到所选路径上的值不可能很大,所以一般(特殊情况等会讨论)有以下几个结论:

  • 最优路径上不可能有点权超过 2 2 2的结点

证明:
设当前已找到的乘积为 x x x,结点个数为 n n n,新加入的点权为 y ( y > 2 ) y(y>2) y(y>2),则新的路权为 x y n + 1 \dfrac{xy}{n+1} n+1xy,由于 y > 2 y>2 y>2,所以一定有 x y n + 1 > x n \dfrac{xy}{n+1}>\dfrac{x}{n} n+1xy>nx,故新路径不可能更优。

  • 最优路径上不可能有超过 1 1 1个点权为 2 2 2的点

证明:
设路径上有 k ( k > 1 ) k(k>1) k(k>1)个权为 2 2 2的结点, n − k ( n ≥ k ) n-k(n\geq k) nk(nk)个权为 1 1 1的结点,则路权为 2 k n \dfrac{2^k}{n} n2k,易得 2 k n ≥ 2 k − 1 n − 1 \dfrac{2^k}{n}\geq\dfrac{2^{k-1}}{n-1} n2kn12k1(当且仅当 n = k = 2 n=k=2 n=k=2时取等号)。

所以,我们只需要找这样的链:只由点权为 1 1 1的点构成,或只由一个点权为 2 2 2的点和若干点权为 1 1 1的点构成。


问题就变成了以下两个步骤:

  1. 找到树上的连续的 1 1 1
  2. 得到最长的一个连续的 1 1 1链,得到一个可能的答案 1 n \dfrac{1}{n} n1 n n n为这个链的长度)
  3. 找到数 2 2 2,看它能否链接两个 1 1 1链,若能,找到最长的两个,用这个 2 2 2把它们连起来,得到一个可能的答案 2 x + y + 1 \dfrac{2}{x+y+1} x+y+12 x x x y y y为这两个 1 1 1链的长度)
  4. 找到最小答案,约分输出

改题的时候我自己写了调了一晚上没弄出来,最后照着std打才理解了这道题实现的奇妙。
无根树的树形DP真的恶心。

设根为结点 1 1 1
定义dpDowndpUp两个数组,dpDown[u]表示以 u u u的儿子 v v v为起点,在 u u u的子树的最长的连续 1 1 1的长度;dpUp[u]表示以 u u u的父亲为起点,不在 u u u子树的最长的连续 1 1 1的长度。
图1
dpDown的转移很简单: d p D o w n [ u ] = max ⁡ v ∈ c h i l d r e n [ u ] { d p D o w n [ v ] + 1 X v = 1 0 X v ≠ 1 dpDown[u]=\max\limits_{v\in children[u]} \begin{cases} dpDown[v]+1& &X_v=1\\ 0& &X_v\neq 1\\ \end{cases} dpDown[u]=vchildren[u]max{dpDown[v]+10Xv=1Xv̸=1

重点是dpUp
发现它可以分成两种情况:

  • 直接转移为dpUp[fa[u]],即不考虑 u u u的兄弟子树,上图表示的就是一种这样的情况
  • 考虑 u u u的兄弟子树,那么就是 u u u的父亲与 u u u的某个兄弟相连,如下图的绿色部分
    图2
    所以: d p U p [ u ] = max ⁡ { d p U p [ f a [ u ] ] , { 0 X f a [ u ] ≠ 1 max ⁡ v ∈ c h i l d r e n [ f a [ u ] ] v ≠ u d p D o w n [ v ] + 1 X f a [ u ] = 1 } dpUp[u]=\max\left\{dpUp[fa[u]], \begin{cases} 0& &X_{fa[u]}\neq 1\\ \max\limits_{v\in children[fa[u]]}^{v\neq u}dpDown[v]+1& &X_{fa[u]}=1 \end{cases} \right\} dpUp[u]=maxdpUp[fa[u]],0vchildren[fa[u]]maxv̸=udpDown[v]+1Xfa[u]̸=1Xfa[u]=1

乍一看是 n 2 n^2 n2的,然而里面那个max显然可以用两个数组优化成 O ( 1 ) O(1) O(1)

  • LeftMax[u]表示先根序中 u u u之前的 u u u的兄弟 b b b 里最大的那个dp[b]
  • RightMax[u]表示先根序中 u u u之后的 u u u的兄弟 b b b 里最大的那个dp[b]

所以: max ⁡ v ∈ c h i l d r e n [ f a [ u ] ] v ≠ u d p [ v ] = max ⁡ { L e f t M a x [ u ] , R i g h t M a x [ u ] } \max\limits_{v\in children[fa[u]]}^{v\neq u}dp[v]=\max\{LeftMax[u],RightMax[u]\} vchildren[fa[u]]maxv̸=udp[v]=max{LeftMax[u],RightMax[u]}

于是给的那 20 % 20\% 20% n ≤ 1000 n\leq 1000 n1000的点就是考虑的最后一步??
这个部分分真的迷,,,


得到了这两个数组就好办了, u u u如果要连接两个链,有两种情况:

  • 一条在 u u u子树,另一条在 u u u头上
  • 两条都在 u u u子树

当然还有一点特殊情况:没有一个点的权值为 1 1 1,那就输出权值最小点的权值就好(可以在DP里面处理掉)。

代码

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;

#define PII pair<int,int>
int gcd(int x,int y){
    return y?gcd(y,x%y):x;
}
bool cmp(PII x,PII y){
    return x.first*y.second<y.first*x.second;
}//比较两个分数的大小

#define MAXN 1000000
int N,W[MAXN+5];
vector<int> G[MAXN+5];

PII Ans(0x7fffffff,1);//first分子 second分母
int dpDown[MAXN+5],dpUp[MAXN+5];
int Lcnt[MAXN+5],Rcnt[MAXN+5];
vector<int> LMax[MAXN+5],RMax[MAXN+5];
void DPDown(int u,int fa){
    LMax[u].push_back(0);
    for(int i=0;i<int(G[u].size());i++){
        int v=G[u][i];
        if(v!=fa){
            DPDown(v,u);
            Lcnt[v]=LMax[u].size()-1;
            LMax[u].push_back(max(LMax[u].back(),(W[v]==1)*(dpDown[v]+1)));
            //注意只有W[x]=1时才有值
        }
    }
    RMax[u].push_back(0);
    for(int i=int(G[u].size())-1;i>=0;i--){
        int v=G[u][i];
        if(v!=fa){
            Rcnt[v]=RMax[u].size()-1;
            RMax[u].push_back(max(RMax[u].back(),(W[v]==1)*(dpDown[v]+1)));
        }
    }
    dpDown[u]=LMax[u].back();
    //得到dpDown,LeftMax和RightMax
}
void DPUp(int u,int fa){
    if(u!=1)//更新dpUp
        dpUp[u]=(W[fa]==1)*(max(dpUp[fa],max(LMax[fa][Lcnt[u]],RMax[fa][Rcnt[u]]))+1);
    PII tmp(W[u],1);
    if(cmp(tmp,Ans))
        Ans=tmp;//处理没有1的情况
    int Max=dpUp[u];
    for(int i=0;i<int(G[u].size());i++){
        int v=G[u][i];
        if(v!=fa){
            if(W[u]<=2){
                int Down=(W[v]==1)*(dpDown[v]+1);
                tmp=make_pair(W[u],Max+Down+1);
                //把u的最大的一条子树链(或头上的链)和当前这一条连起来
                if(cmp(tmp,Ans))
                    Ans=tmp;
                Max=max(Max,Down);
            }
            DPUp(v,u);//这里其实是往下走
        }
    }
}

int main(){
    freopen("mag.in" ,"r", stdin);
    freopen("mag.out","w",stdout);
    scanf("%d",&N);
    for(int i=1;i<=N-1;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    for(int i=1;i<=N;i++)
        scanf("%d",&W[i]);
    DPDown(1,-1),DPUp(1,-1);
    int d=gcd(Ans.first,Ans.second);
    printf("%d/%d",Ans.first/d,Ans.second/d);//约分
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值