LOJ3052 「十二省聯考2019」春節十二響 啓發式合併

題目鏈接:傳送門
注意這裏題目意思是若x,yx,y在同一段中,那麼xx不能是yy祖先yy也不能是xx祖先。一開始被坑了一會……

首先吐槽一下,本題數據奇水,爆搜+剪枝+特判鏈情況能拿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;
}

直接做有困難,這裏從鏈的情況入手:
在這裏插入圖片描述
這裏左邊2,3,42,3,4構成一個序列,5,65,6構成一個序列。貪心的策略是把序列排序,讓儘量讓值大的和值大的合併。
正確性證明:
假設序列1最大值是aa,次大值是bb,序列2最大值是cc,次大值是dda&gt;=b,c&gt;=da&gt;=b,c&gt;=d)。
不妨設a&gt;=ca&gt;=c
儘量讓值大的合併的答案是a+max(b,d)a+max(b,d)
如果不讓最大值和最大值合併,那麼假設讓最大值和次大值合併,則答案是a+max(b,c)a+max(b,c)
因爲c&gt;=dc&gt;=d,所以max(b,c)&gt;=max(b,d)max(b,c)&gt;=max(b,d),那麼讓最大值和最大值合並不會使答案更不優。

然後考慮推廣到樹上:
假設當前搜到的節點是xx,正在遍歷yy的子樹。
發現若yy有一種分段方法,那麼這種分段方法珂以和之前子樹的分段方法合併。
因爲yy的子樹內任意節點和之前子樹內的任意節點的LCA都是xx,而xx還並沒有分到一段中(參照鏈的處理方法珂以知道xx最後單獨分爲一段)。
所以每個節點按貪心策略維護每一段的答案,啓發式合併即珂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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章