【校内模拟】Gosling(区间DP)(括号序列)(DSU on tree)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/zxyoi_dreamer/article/details/102709287

简要题意:

给你两棵有根树,边有边权,请你使用下面两个操作使得这两棵树同构。

在这两棵有根树中,每个节点的孩子有序排列形成一个孩子列表。

这里同构的定义如下:

  1. 两个单独的节点同构。
  2. 两个有根树同构,当且仅当它们的孩子数量相同,且孩子按照在孩子列表中的顺序对应同构,对应孩子的指向父亲的边权相同。

你能使用的操作用三种,有两个代价参数 c 1 , c 2 c_1,c_2 ,含义在下述的操作中描述。

  1. 伸展,从 u u 的孩子列表中选择一段区间(可以为空,这时候指定位置即可),生成一个新节点 v v ,与 u u 连权值为 w w 的边, u u 的这段孩子成为 v v 的孩子,且 v v 插入到孩子列表中这一部分。这个操作代价为 c 1 w c_1w
  2. 收缩,选择一个节点 u u ,然后选择 u u 的一个孩子 v v ,将 v v 删除,并将 v v 的孩子列表接到 v v u u 的孩子列表中的位置。设 v v 连向 u u 的边权为 w w ,这个操作的代价为 c 1 w c_1w
  3. 调整,选择一条边更改其权值,设其原来的权值为 w 1 w_1 ,更改后的权值为 w 2 w_2 ,这次操作的代价为 w 1 w 2 c 2 |w_1-w_2|c_2 伸展产生的边不能被调整,被调整的边不能被收缩。

请你最小化操作代价。

题解:

孩子有序,可以考虑括号序列。

注意到伸展和收缩的代价系数是相等的,这启示我们去思考它们之间的相似之处。

显然在第一个树上伸展可以等价为在第二个树上收缩。那么我们只考虑收缩。

把所有点到父亲的权值放到括号上,性质就非常明显。

收缩显然就是删去一对括号,调整显然就是调括号的权值,注意不能对根节点的括号进行任何操作,要求最后两个括号序列匹配(包括权值)。

直接考虑区间DP,转移非常显然直接考虑这个括号删不删就行了,注意两棵树都删这个括号的情况会被先删一个再删一个地考虑到,就不需要特殊处理了。

直接转移需要枚举上下的所有合法括号序列,复杂度是 O ( n 1 2 n 2 2 ) O(n_1^2n_2^2) ,其中 n 1 , n 2 n_1,n_2 分别是两棵树的大小。

注意到转移顺序和最终结果是没有关系的。

对于一个括号区间,考虑它的前缀括号和后缀括号,把小的那个扔掉,先处理剩下的区间,然后考虑把小的括号的每个子区间拿来和剩下的进行拼接。

这样子最终剩下的显然是重儿子,需要转移的区间个数就是 O ( ) O(所有点的轻儿子大小之和) 。也就是 O ( n 2 log n 2 ) O(n_2\log n_2) ,当然对于第一棵树还是要考虑所有区间。

本质上就是在用DSU on tree的策略来进行合并。

实现的话,我是直接把所有合法的括号区间拿出来从小到大转移,这样一个区间的子区间显然会先被考虑到。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int N1=55,N2=2e3+7;

int c1,c2,n1,n2;
struct edge{int to,w;}; 
std::vector<edge> G1[N1],G2[N2];

char s1[N1<<1|1],s2[N2<<1|1];//brackets sequences
int p1[N1<<1|1],p2[N2<<1|1];//position of the other bracket
int w1[N1<<1|1],w2[N2<<1|1];//weight of the edge to father

int now;//the length of filled sequence
void pre_dfs(int u,char *s,int *p,int *w,std::vector<edge> *G,int c=0){
	s[++now]='(';int p1=now;
	for(auto &e:G[u])pre_dfs(e.to,s,p,w,G,e.w);
	s[++now]=')';int p2=now;
	p[p1]=p2,p[p2]=p1;w[p1]=w[p2]=c;
}

std::vector<int> L1,R1,L2,R2,*L,*R;
int id1[N1<<1|1][N1<<1|1],id2[N2<<1|1][N2<<1|1];

inline int &id(int l,int r){return now==1?id1[l][r]:id2[l][r];}

int dfs(int l,int r,char *s,int *p){
	if(~id(l,r))return id(l,r);if(l>r)return id(l,r)=0;
	if(s[l]==')'||p[l]>r)return id(l,r)=dfs(l+1,r,s,p);
	if(s[r]=='('||p[r]<l)return id(l,r)=dfs(l,r-1,s,p);
	if(now==1){
		dfs(l+1,r,s,p);
		dfs(l+1,p[l]-1,s,p);
		dfs(l,r-1,s,p);
		dfs(p[r]+1,r-1,s,p);
	}else {
		if(p[l]-l<r-p[r]){
			dfs(l+1,r,s,p);
			dfs(l+1,p[l]-1,s,p);
		}else {
			dfs(l,r-1,s,p);
			dfs(p[r]+1,r-1,s,p);
		}
	}
	id(l,r)=L->size();L->push_back(l),R->push_back(r);
	return id(l,r);
}

template<class T>
inline void get_range(T id,int n,char *s,int *p){
	for(int re l=n+1;l>=1;--l)
	for(int re r=l-1;r<=n;++r)if(!~id[l][r]){
		if(l>r)id[l][r]=0;
		else if(s[l]==')'||p[l]>r)id[l][r]=id[l+1][r];
		else if(s[r]=='('||p[r]<l)id[l][r]=id[l][r-1];
//		else cerr<<"WTF\n"; 
	}
}

ll F[N1*N1][N2*20+7];
inline ll &G(int l1,int r1,int l2,int r2){return F[id1[l1][r1]][id2[l2][r2]];}
inline ll cost(int w){return (ll)c1*w;}
inline ll cost(int w1,int w2){return (ll)c2*abs(w1-w2);}
inline void ckmin(ll &a,ll b){a>b?a=b:0;}

signed main(){
#ifdef zxyoi
	freopen("gosling.in","r",stdin);
#endif
	c1=gi(),c2=gi();n1=gi();
	for(int re i=1;i<=n1;++i){
		int k=gi();G1[i].resize(k);
		for(int re j=0;j<k;++j){
			int v=gi(),w=gi();
			G1[i][j]=(edge){v,w};
		}
	}n2=gi();
	for(int re i=1;i<=n2;++i){
		int k=gi();G2[i].resize(k);
		for(int re j=0;j<k;++j){
			int v=gi(),w=gi();
			G2[i][j]=(edge){v,w};
		}
	}
	now=0;pre_dfs(1,s1,p1,w1,G1);
	now=0;pre_dfs(1,s2,p2,w2,G2);
	memset(id1,-1,sizeof id1);memset(id2,-1,sizeof id2);
	L1.push_back(1),L2.push_back(1),R1.push_back(0),R2.push_back(0);
	L=&L1,R=&R1;now=1;dfs(1,n1<<1,s1,p1);get_range(id1,n1<<1,s1,p1);
	L=&L2,R=&R2;now=2;dfs(1,n2<<1,s2,p2);get_range(id2,n2<<1,s2,p2);
	std::vector<int> rg1,rg2;rg1.resize(L1.size());rg2.resize(L2.size());
	for(int re i=0;i<L1.size();++i)rg1[i]=i;
	for(int re i=0;i<L2.size();++i)rg2[i]=i;
	std::sort(rg1.begin(),rg1.end(),[](int i,int j){return R1[i]-L1[i]<R1[j]-L1[j];});
	std::sort(rg2.begin(),rg2.end(),[](int i,int j){return R2[i]-L2[i]<R2[j]-L2[j];});
	for(int re i:rg1)for(int re j:rg2){
		int l1=L1[i],r1=R1[i],l2=L2[j],r2=R2[j];
		ll &res=G(l1,r1,l2,r2);
		if(l1>r1&&l2>r2)res=0;
		else {
			res=1ll<<60;
			if(p2[l2]-l2<r2-p2[r2]){
				if(l1<=r1&&l2<=r2)
					ckmin(res,G(l1+1,p1[l1]-1,l2+1,p2[l2]-1)+G(p1[l1]+1,r1,p2[l2]+1,r2)+cost(w1[l1],w2[l2]));
				if(l1<=r1)ckmin(res,G(l1+1,r1,l2,r2)+cost(w1[l1]));
				if(l2<=r2)ckmin(res,G(l1,r1,l2+1,r2)+cost(w2[l2]));
			}else {
				if(l1<=r1&&l2<=r2)
					ckmin(res,G(l1,p1[r1]-1,l2,p2[r2]-1)+G(p1[r1]+1,r1-1,p2[r2]+1,r2-1)+cost(w1[r1],w2[r2]));
				if(l1<=r1)ckmin(res,G(l1,r1-1,l2,r2)+cost(w1[r1]));
				if(l2<=r2)ckmin(res,G(l1,r1,l2,r2-1)+cost(w2[r2]));
			}
		}
	}
	cout<<G(2,n1+n1-1,2,n2+n2-1)<<"\n";
	return 0;
}

猜你喜欢

转载自blog.csdn.net/zxyoi_dreamer/article/details/102709287