題目鏈接:傳送門
注意這裏題目意思是若在同一段中,那麼不能是的祖先,也不能是的祖先。一開始被坑了一會……
首先吐槽一下,本題數據奇水,爆搜+剪枝+特判鏈情況能拿60分
暴力就不說了,具體看代碼即珂(亂剪枝就行)
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
const int Size=200005;
int n,cnt,head[Size],m[Size];
struct Edge {
int v,next;
} w[Size<<1];
void AddEdge(int u,int v) {
w[++cnt].v=v;
w[cnt].next=head[u];
head[u]=cnt;
}
bool IsChain=true;
namespace Situation1 {
int L[Size],R[Size];
int cntl,cntr;
void dfs(int x,bool f) {
if(f) {
L[++cntl]=m[x];
} else {
R[++cntr]=m[x];
}
int cnt=0;
for(int i=head[x]; i; i=w[i].next) {
dfs(w[i].v,f);
if(!IsChain) return;
cnt++;
if(cnt>1) {
IsChain=false;
return;
}
}
}
inline bool comp(int x,int y) {
return x>y;
}
void Solution() {
sort(L+1,L+1+cntl,comp);
sort(R+1,R+1+cntr,comp);
int len=min(cntl,cntr);
ll ans=0;
for(re i=1; i<=len; i++) {
ans+=max(L[i],R[i]);
}
for(re i=len+1; i<=cntl; i++) {
ans+=L[i];
}
for(re i=len+1; i<=cntr; i++) {
ans+=R[i];
}
printf("%lld",ans+m[1]);
}
}
namespace Situation2 {
const int Maxn=2005;
int maxd,top,stk[Size];
bool vis[Maxn][Maxn]; //若vis[i][j]爲真,則i和j不能在同一段中
void dfs(int x,int d) {
//dfs處理兩個東西:1.最大深度maxd 2.處理出哪些點不能在同一段中
for(re i=1; i<=top; i++) {
vis[stk[i]][x]=vis[x][stk[i]]=true;
}
stk[++top]=x;
if(d>maxd) maxd=d;
for(int i=head[x]; i; i=w[i].next) {
int nxt=w[i].v;
dfs(nxt,d+1);
}
top--;
}
int siz[Maxn],val[Maxn],group[Maxn][Maxn];
bool check(int x,int g) {
//判斷x能不能放在g組裏
for(re i=1; i<=siz[g]; i++) {
if(vis[x][group[g][i]]) {
return false;
}
}
return true;
}
ll ans=2e18;
void srch(int now,int all,int num,ll sum) {
//胡亂剪枝
if(sum>=ans) return;
if((n-now+1)+num<all) return;
if(now==n+1) {
if(sum<ans) ans=sum;
return;
}
for(int i=2; i<=all; i++) {
if(check(now,i)) {
group[i][++siz[i]]=now;
int tmpv=val[i];
val[i]=max(val[i],m[now]);
if(siz[i]==1) {
srch(now+1,all,num+1,sum-tmpv+val[i]);
} else {
srch(now+1,all,num,sum-tmpv+val[i]);
}
val[i]=tmpv;
siz[i]--;
}
}
}
void Solution() {
dfs(1,1);
group[1][1]=siz[1]=1;
val[1]=m[1];
srch(2,maxd,1,0); //強制讓1在第一段,然後枚舉2以後的節點在哪一段,一共maxd段
printf("%lld",ans+m[1]);
}
}
int main() {
n=read();
for(re i=1; i<=n; i++) {
m[i]=read();
}
for(re i=2; i<=n; i++) {
int fa=read();
AddEdge(fa,i);
}
bool now=true;
int cnt=0;
for(int i=head[1]; i; i=w[i].next) {
Situation1::dfs(w[i].v,now);
now=!now;
cnt++;
}
if(cnt==2 && IsChain) {
Situation1::Solution();
} else {
Situation2::Solution();
}
return 0;
}
直接做有困難,這裏從鏈的情況入手:
這裏左邊構成一個序列,構成一個序列。貪心的策略是把序列排序,讓儘量讓值大的和值大的合併。
正確性證明:
假設序列1最大值是,次大值是,序列2最大值是,次大值是()。
不妨設。
儘量讓值大的合併的答案是
如果不讓最大值和最大值合併,那麼假設讓最大值和次大值合併,則答案是
因爲,所以,那麼讓最大值和最大值合並不會使答案更不優。
然後考慮推廣到樹上:
假設當前搜到的節點是,正在遍歷的子樹。
發現若有一種分段方法,那麼這種分段方法珂以和之前子樹的分段方法合併。
因爲的子樹內任意節點和之前子樹內的任意節點的LCA都是,而還並沒有分到一段中(參照鏈的處理方法珂以知道最後單獨分爲一段)。
所以每個節點按貪心策略維護每一段的答案,啓發式合併即珂qwq。
毒瘤代碼
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
inline void write(const int x) {
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int Size=200005;
int n,cnt,head[Size],m[Size];
struct Edge {
int v,next;
} w[Size<<1];
void AddEdge(int u,int v) {
w[++cnt].v=v;
w[cnt].next=head[u];
head[u]=cnt;
}
priority_queue<int> Q[Size];
int stk[Size];
inline void Union(int x,int y) {
//毒瘤啓發式合併
if(Q[x].size()<Q[y].size()) swap(Q[x],Q[y]);
re top=0;
while(!Q[y].empty()) {
stk[++top]=max(Q[x].top(),Q[y].top());
Q[x].pop();
Q[y].pop();
}
for(re i=1; i<=top; i++) {
Q[x].push(stk[i]);
}
}
void dfs(int x) {
for(int i=head[x]; i; i=w[i].next) {
dfs(w[i].v);
Union(x,w[i].v);
}
Q[x].push(m[x]);
}
int main() {
n=read();
for(re i=1; i<=n; i++) {
m[i]=read();
}
for(re i=2; i<=n; i++) {
int fa=read();
AddEdge(fa,i);
}
dfs(1);
ll ans=0;
//因爲Q[1]中維護的是以1爲根的每一段的答案,所以要都加起來qwq
while(!Q[1].empty()) {
ans+=Q[1].top();
Q[1].pop();
}
printf("%lld",ans);
return 0;
}