LOJ#2509. 「AHOI / HNOI2018」排列
这个题实质上是一棵树上满足父亲比儿子先选,第i个选的点
x
x
x的有
i
∗
w
[
x
]
i*w[x]
i∗w[x]的贡献,求最大贡献。
可以发现
w
[
x
]
w[x]
w[x]最小的应该尽可能先的被选。
于是我们考虑将最小的点
x
x
x和
f
a
x
fa_x
fax缩为一个点。
然后我们考虑到缩为一个点之后的新
w
w
w是一个问题。
考虑两个没有限制的树的先后选关系,缩为两个点之后分别为
(
a
,
b
)
,
(
c
,
d
)
(a,b),(c,d)
(a,b),(c,d)
其中
(
x
,
y
)
(x,y)
(x,y)表示大小为
x
x
x,
w
w
w和为
y
y
y。
首先我们可以举一些小例子,比如一个树是
w
[
x
]
=
1
−
>
w
[
y
]
=
2
w[x] = 1-> w[y] = 2
w[x]=1−>w[y]=2,发现另一个树是
w
[
z
]
=
1.5
w[z] = 1.5
w[z]=1.5时先后选没有区别。
那么就猜测先后选与平均值有关系。
去dalao博客看证明吧
经过yy可以发现这是对的。
那么对于缩完点后的
(
x
,
y
)
(x,y)
(x,y)以
y
x
\frac yx
xy为关键字每次取最小的和父亲合并。
使用set维护。
AC Code:
#include<bits/stdc++.h>
#define maxn 500005
#define LL long long
using namespace std;
int n,w[maxn],a[maxn];
int info[maxn],Prev[maxn],to[maxn],cnt_e;
void Node(int u,int v){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v; }
struct node{
int l,id;LL s,a;
node(int l=0,int id=0,LL s=0,LL a=0):l(l),id(id),s(s),a(a){}
bool operator <(const node &B)const{ return s*B.l == l*B.s ? id < B.id : s*B.l < l*B.s; }
node operator +(const node &B)const{ return node(l+B.l,id,s+B.s,a+B.a+B.s*l); }
}ar[maxn];
int F[maxn];
int Find(int now){ return F[now] == -1 ? now : F[now] = Find(F[now]); }
int main(){
scanf("%d",&n);
int rt = 0;
for(int i=1;i<=n;i++) scanf("%d",&a[i]) , (!a[i]) && (rt = i);
multiset<node>st;
for(int i=1;i<=n;i++) scanf("%d",&w[i]),st.insert(ar[i] = node(1,i,w[i],w[i]));
st.insert(ar[0] = node(0,0,0,0));
if(!rt){ puts("-1");return 0; }//它居然过了这水数据
memset(F,-1,sizeof F);
for(;!st.empty();){
int lc = (*st.begin()).id , y = Find(a[lc]);
st.erase(st.begin());
if(!lc) continue;
if(y) st.erase(st.find(ar[y]));
ar[y] = ar[y] + ar[lc];
F[lc] = y;
if(y) st.insert(ar[y]);
}
printf("%lld\n",ar[0].a);
}
合并果子
⋅
\cdot
⋅改
小象有
n
n
n堆果子排成一列,每堆果子有个权值。小象一开始可以选择一堆果子。接下来每一轮,小象可以选择将这堆果子与左边或者右边的果子合并,形成一堆新的果子,在新的果子上继续进行上面的操作。进行
n
−
1
n-1
n−1轮后,合并成一堆果子。每次合并两堆果子的代价为两堆果子的重量之和。
小象想知道,对于每堆果子,如果小象选择以这堆果子作为初始选择,那么合并的最小代价是什么。
n
<
=
2
e
5
n<=2e5
n<=2e5
这个发现也是一棵树,求和的式子推一下也一样。
不过是根有两个儿子,其余点只有一个儿子的那种树。
但是我们需要对于链上每个点为根都求答案,之前的方法就不管用了。
这个题有一个很巧也很短的 O ( n 2 ) O(n^2) O(n2)解法:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n,a[5005];
LL sum[5005],dp[5005][5005];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=sum[i-1] + a[i];
memset(dp,0x3f,sizeof dp);
dp[1][n] = 0;
for(int l=n-1;l>=1;l--)
for(int st=1;st+l<=n;st++)
dp[st+1][st+l] = min(dp[st+1][st+l] , dp[st][st+l] + sum[st+l] - sum[st-1]),
dp[st][st+l-1] = min(dp[st][st+l-1] , dp[st][st+l] + sum[st+l] - sum[st-1]);
for(int i=1;i<=n;i++)
printf("%lld\n",dp[i][i]);
}
但是毒瘤的dls把它出到了
2
e
5
2e5
2e5
考虑每个点向上合并的过程。
那么对于根左边链上的点,是多条连续的子链分批次的合并到根上,这些子链
(
x
,
y
)
(x,y)
(x,y)中
y
x
\frac yx
xy按从小到大的顺序合并到根上。左右两边链构成了一个归并关系。
这个归并其实并没有那么好的性质,我们直接用平衡树可以求最大值,同时支持插入删除
(
x
,
y
)
(x,y)
(x,y)。
然后我们可以单调栈求出每一个
(
x
,
y
)
(x,y)
(x,y)根在哪个位置时开始出现,根在哪个位置时结束出现(可以发现每个
(
x
,
y
)
(x,y)
(x,y)出现的位置是连续的)
然后再扫一遍维护平衡树来插删
(
x
,
y
)
(x,y)
(x,y)求每个点为根的答案。
毒瘤。
AC Code:
#include<bits/stdc++.h>
#define maxn 500005
#define LL long long
using namespace std;
int n,w[maxn],a[maxn];
int info[maxn],Prev[maxn],to[maxn],cnt_e;
void Node(int u,int v){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v; }
struct node{
int l,id;LL s,a;
node(int l=0,int id=0,LL s=0,LL a=0):l(l),id(id),s(s),a(a){}
bool operator <(const node &B)const{ return s*B.l == l*B.s ? id < B.id : s*B.l < l*B.s; }
node operator +(const node &B)const{ return node(l+B.l,id,s+B.s,a+B.a+B.s*l); }
}ar[maxn];
int F[maxn];
int Find(int now){ return F[now] == -1 ? now : F[now] = Find(F[now]); }
int main(){
scanf("%d",&n);
int rt = 0;
for(int i=1;i<=n;i++) scanf("%d",&a[i]) , (!a[i]) && (rt = i);
multiset<node>st;
for(int i=1;i<=n;i++) scanf("%d",&w[i]),st.insert(ar[i] = node(1,i,w[i],w[i]));
st.insert(ar[0] = node(0,0,0,0));
if(!rt){ puts("-1");return 0; }
memset(F,-1,sizeof F);
for(;!st.empty();){
int lc = (*st.begin()).id , y = Find(a[lc]);
st.erase(st.begin());
if(!lc) continue;
if(y) st.erase(st.find(ar[y]));
ar[y] = ar[y] + ar[lc];
F[lc] = y;
if(y) st.insert(ar[y]);
}
printf("%lld\n",ar[0].a);
}