既然是乘积,容易想到所选路径上的值不可能很大,所以一般(特殊情况等会讨论)有以下几个结论:
证明:
设当前已找到的乘积为 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,故新路径不可能更优。
证明:
设路径上有 k ( k > 1 ) k(k>1) k(k>1)个权为 2 2 2的结点, n − k ( n ≥ k ) n-k(n\geq k) n−k(n≥k)个权为 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} n2k≥n−12k−1(当且仅当 n = k = 2 n=k=2 n=k=2时取等号)。
所以,我们只需要找这样的链:只由点权为 1 1 1的点构成,或只由一个点权为 2 2 2的点和若干点权为 1 1 1的点构成。
问题就变成了以下两个步骤:
改题的时候我自己写了调了一晚上没弄出来,最后照着std
打才理解了这道题实现的奇妙。
无根树的树形DP真的恶心。
设根为结点 1 1 1,
定义dpDown
和dpUp
两个数组,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的长度。
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]=v∈children[u]max{dpDown[v]+10Xv=1Xv̸=1
重点是dpUp
:
发现它可以分成两种情况:
dpUp[fa[u]]
,即不考虑 u u u的兄弟子树,上图表示的就是一种这样的情况乍一看是 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]\} v∈children[fa[u]]maxv̸=udp[v]=max{LeftMax[u],RightMax[u]}
于是给的那 20 % 20\% 20%的 n ≤ 1000 n\leq 1000 n≤1000的点就是考虑的最后一步??
这个部分分真的迷,,,
得到了这两个数组就好办了, u u u如果要连接两个链,有两种情况:
当然还有一点特殊情况:没有一个点的权值为 1 1 1,那就输出权值最小点的权值就好(可以在DP里面处理掉)。
#include
#include
#include
#include
using namespace std;
#define PII pair
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);//约分
}