HDU6094 Rikka with K-Match 和 Gym102331J Jiry Matchings

Rikka with K-Match

Yuta has a graph (G) with (n) nodes ((i,j)(1 leq i leq n,1 leq j leq m)). There is an edge between ((a,b)) and ((c,d)) if and only if (|a-c|+|b-d|=1). Each edge has its weight.

Now Yuta wants to calculate the minimum weight (K)-matching of (G).

(1 leq n leq 4 imes 10^4,1 leq m leq 4)

题解

https://blog.csdn.net/The_star_is_at/article/details/76919415

应该是一个经典的 Idea 了。令 (w_i) 为匹配数为 (i) 时的最小匹配,那么因为匹配是一个费用流问题,所以一定有 (w_{i+1}-w_{i} geq w_{i}-w_{i-1})

考虑把 (w_i) 看成平面上的点 ((i,w_i)),那么显然所有点都分布在一个下凸壳上,因此肯定存在一个斜率 (d),使得这个斜率的直线与下凸壳的切点恰好为 ((K,w_K)),即 ((K,w_K))(w_i-id) 最小的点,这相当于把边权减去 (d) 后的最小匹配。

因此可以二分斜率 (d),然后求出边权全部减去 (d) 后的最小匹配的值以及最小匹配中有多少条边,根据最小匹配中的边数和 (K) 的大小关系来决定二分的方向。这样问题就转化成了求 (O(log n)) 次网格图最大匹配。这是一个轮廓线 DP 的经典问题,可以在 (O(nm2^m)) 内解决。

因此总的时间复杂度为 (O(nm2^mlog n))

要注意的是二分上界不能设为 (10^9) 级别,考虑一条 (1)(10^9) 交错的链,那么在最后一次增广的时候所有的 (1) 都会变成 (10^9),因此二分上界应该是 (10^9 imes frac{nm}{2}),标程把上界设为了 (10^{14}),DP 时刚好不会爆 long long

CO int64 inf=1e18;
int n,m,A[40001][4],B[40001][4];
int64 f[2][5][1<<4];int g[2][5][1<<4];

pair<int64,int> solve(int64 mid){
	for(int i=0;i<=m;++i) fill(f[0][i],f[0][i]+(1<<m),inf);
	f[0][m][(1<<m)-1]=g[0][m][(1<<m)-1]=0;
	int o=1;
	for(int u=1;u<=n;++u){
		for(int i=0;i<=m;++i) fill(f[o][i],f[o][i]+(1<<m),inf);
		copy(f[o^1][m],f[o^1][m]+(1<<m),f[o][0]);
		copy(g[o^1][m],g[o^1][m]+(1<<m),g[o][0]);
		for(int i=0;i<m;++i)for(int s=0;s<1<<m;++s){
			if(i<m-1){
				int t=s|1<<i|1<<(i+1);
				int64 v=f[o][i][s]+B[u][i]-mid;
				int c=g[o][i][s]+1;
				if(f[o][i+2][t]>v or (f[o][i+2][t]==v and g[o][i+2][t]<c))
				  	f[o][i+2][t]=v,g[o][i+2][t]=c;
			}
			if(u>1 and ~s>>i&1){
				int t=s|1<<i;
				int64 v=f[o][i][s]+A[u-1][i]-mid;
				int c=g[o][i][s]+1;
				if(f[o][i+1][t]>v or (f[o][i+1][t]==v and g[o][i+1][t]<c))
					f[o][i+1][t]=v,g[o][i+1][t]=c;
			}
			int t=s&~(1<<i);
			int64 v=f[o][i][s];
			int c=g[o][i][s];
			if(f[o][i+1][t]>v or (f[o][i+1][t]==v and g[o][i+1][t]<c))
			  	f[o][i+1][t]=v,g[o][i+1][t]=c;
		}
		o^=1;
	}
	int64 ans=inf;int K=0;
	for(int s=0;s<1<<m;++s)
		if(ans>f[o^1][m][s] or (ans==f[o^1][m][s] and K<g[o^1][m][s]))
			ans=f[o^1][m][s],K=g[o^1][m][s];
	return make_pair(ans,K);
}
void real_main(){
	read(n),read(m);
	int K=read<int>();
	for(int i=1;i<n;++i)for(int j=0;j<m;++j) read(A[i][j]);
	for(int i=1;i<=n;++i)for(int j=0;j<m-1;++j) read(B[i][j]);
	int64 l=0,r=1e14;
	while(l<r){
		int64 mid=(l+r)>>1;
		if(solve(mid).second>=K) r=mid;
		else l=mid+1;
	}
	printf("%lld
",solve(l).first+l*K);
}
int main(){
	for(int T=read<int>();T--;) real_main();
	return 0;
}

Jiry Matchings

给一棵边权树,(f(i)) 表示 (i) 条边的最大匹配。求出 (f(1),f(2),dots,f(n-1))

(n leq 2 imes 10^5),时限6s。

费用流

这是个二分图最大权匹配的问题,有经典费用流算法。

CO int N=2e5+10;
namespace flow{
	int n,S,T;
	struct edge {int v,c,w,a;};
	vector<edge> to[N];
	int64 dis[N];int vis[N];
	
	IN void init(int n){
		flow::n=n,S=n-1,T=n;
	}
	IN void link(int u,int v,int c,int w){
		to[u].push_back({v,c,w}),to[v].push_back({u,0,-w});
		to[u].back().a=to[v].size()-1,to[v].back().a=to[u].size()-1;
	}
	bool bfs(){
		fill(dis+1,dis+n+1,-1e18),dis[T]=0;
		deque<int> Q={T};vis[T]=1;
		while(Q.size()){
			int u=Q.front();
			Q.pop_front(),vis[u]=0;
			for(CO edge&e:to[u])if(to[e.v][e.a].c and dis[e.v]<dis[u]-e.w){
				dis[e.v]=dis[u]-e.w;
				if(vis[e.v]) continue;
				if(Q.size() and dis[e.v]>=dis[Q.front()]) Q.push_front(e.v);
				else Q.push_back(e.v);
				vis[e.v]=1;
			}
		}
		return dis[S]>-1e18;
	}
	int dfs(int u,int lim){
		if(u==T) return lim;
		vis[u]=1;
		int rest=lim;
		for(edge&e:to[u])if(!vis[e.v] and e.c and dis[e.v]==dis[u]-e.w){
			int delta=dfs(e.v,min(e.c,rest));
			if(!delta) {dis[e.v]=-1e18;continue;}
			rest-=delta,e.c-=delta,to[e.v][e.a].c+=delta;
			if(!rest) break;
		}
		vis[u]=0;
		return lim-rest;
	}
	vector<int64> main(){
		vector<int64> ans={0};
		while(bfs()){
			int len=dfs(S,1e9);
			for(int i=1;i<=len;++i) ans.push_back(dis[S]);
		}
		for(int i=1;i<(int)ans.size();++i) ans[i]+=ans[i-1];
		return ans;
	}
}

struct edge {int v,w;};
vector<edge> to[N];

void dfs(int u,int fa,int dep){
	if(dep&1) flow::link(u,flow::T,1,0);
	else flow::link(flow::S,u,1,0);
	for(CO edge&e:to[u])if(e.v!=fa){
		if(dep&1) flow::link(e.v,u,1,e.w);
		else flow::link(u,e.v,1,e.w);
		dfs(e.v,u,dep+1);
	}
}
int main(){
	int n=read<int>();
	for(int i=1;i<n;++i){
		int u=read<int>(),v=read<int>(),w=read<int>();
		to[u].push_back({v,w}),to[v].push_back({u,w});
	}
	flow::init(n+2);
	dfs(1,0,0);
	vector<int64> ans=flow::main();
	for(int i=1;i<(int)ans.size();++i) printf("%lld%c",ans[i]," 
"[i==n-1]);
	for(int i=ans.size();i<n;++i) printf("?%c"," 
"[i==n-1]);
	return 0;
}

整体带权二分

可以用费用流解决说明这个 (f(i)) 是个上凸函数。那么尝试用带权二分+整体二分解决。

https://www.mina.moe/archives/6349

现在我们就可以说明第一个问题:我们是否可能无法找到恰好 k 段的情况。

如果函数图像存在共线的三点,那么中间的那个点往往取不到。因为无论如何一条直线都不会与那个点相切。因此那个点横坐标代表的段数是永远取不到的。不过,如果我们找到了与他共线的那些点的最左边的点,我们就可以通过左边那个点直接推出他的答案,因此这个问题解决了。

struct node {int128 x;int y;};
IN bool operator<(CO node&a,CO node&b){
	return a.x!=b.x?a.x<b.x:a.y>b.y; // as few as possible
}
IN node operator+(CO node&a,CO node&b){
	return {a.x+b.x,a.y+b.y};
}
 
CO int N=2e5+10;
struct edge {int v,w;};
vector<edge> to[N];
int cnt[N][2],que[N];
 
void dfs(int u,int fa){
	cnt[u][0]=cnt[u][1]=0;
	for(int i=0;i<(int)to[u].size();++i){
		int v=to[u][i].v;
		if(v==fa){
			to[u].erase(to[u].begin()+i),--i;
			continue;
		}
		dfs(v,u);
		cnt[u][1]+=max(cnt[v][0],cnt[v][1]);
		cnt[u][1]=max(cnt[u][1],cnt[u][0]+cnt[v][0]+1);
		cnt[u][0]+=max(cnt[v][0],cnt[v][1]);
	}
	que[++que[0]]=u;
}
 
node dp[N][2];
 
void bfs(int128 cost){
	for(int i=1;i<=que[0];++i){
		int u=que[i];
		dp[u][0]=dp[u][1]={0,0};
		for(CO edge&e:to[u]){
			dp[u][1]=dp[u][1]+max(dp[e.v][0],dp[e.v][1]);
			dp[u][1]=max(dp[u][1],dp[u][0]+dp[e.v][0]+(node){e.w-cost,1});
			dp[u][0]=dp[u][0]+max(dp[e.v][0],dp[e.v][1]);
		}
	}
}
 
int64 ans[N];
 
void solve(int l,int r,int128 L,int128 R){
	if(l>r) return;
	if(L==R){
		bfs(L);
		node res=max(dp[1][0],dp[1][1]);
		for(int i=l;i<=r;++i) ans[i]=res.x+L*i;
		return;
	}
	int128 MID=(L+R)>>1;
	bfs(MID);
	node res=max(dp[1][0],dp[1][1]);
	int mid=l;
	while(mid<=r and res.y>mid) ++mid;
	solve(l,mid-1,MID+1,R);
	solve(mid,r,L,MID);
}
int main(){
	int n=read<int>();
	for(int i=1;i<n;++i){
		int u=read<int>(),v=read<int>(),w=read<int>();
		to[u].push_back({v,w}),to[v].push_back({u,w});
	}
	dfs(1,0);
	int lim=max(cnt[1][0],cnt[1][1]);
	solve(1,lim,-2e14,1e9);
	for(int i=1;i<=lim;++i) printf("%lld%c",ans[i]," 
"[i==n-1]);
	for(int i=lim+1;i<n;++i) printf("?%c"," 
"[i==n-1]);
	return 0;
}

然而它TLE了。究其所以然,它的复杂度是假的。因为每次二分的时候 (T(len)=O(n)),所以总时间复杂度 (O(n^2log n))

重链分治

正确做法是考虑重链分治。

https://blog.csdn.net/Zayin___/article/details/104084068

考虑如何做两个凸函数的max +卷积,可以考虑差分后归并。

再考虑一个经典的树上分治FFT做法,即对轻儿子做分治FFT,对所有重链做分治FFT。把上面那个做法套上去就可以了。

时间复杂度 (O(n log^2 n))

CO int N=2e5+10;
CO int64 inf=1e18;
struct edge {int v,w;};
vector<edge> to[N];
int fa[N],val[N],siz[N],son[N];

poly f[2][N],g[2][2][N];

poly merge(poly a,poly b){
	if(!a.size()) return b;
	if(!b.size()) return a;
	for(int i=a.size()-1;i>=1;--i) a[i]-=a[i-1];
	for(int i=b.size()-1;i>=1;--i) b[i]-=b[i-1];
	poly res={a.front()+b.front()};
	int l=1,r=1;
	while(l<(int)a.size() or r<(int)b.size()){
		if(r==(int)b.size() or (l<(int)a.size() and a[l]>b[r]))
			res.push_back(a[l]),++l;
		else res.push_back(b[r]),++r;
	}
	for(int i=1;i<(int)res.size();++i) res[i]+=res[i-1];
	return res;
}
poly shift(CO poly&a,int64 x){
	vector<int64> res={-inf}; // can't choose nothing
	for(int i=0;i<(int)a.size();++i) res.push_back(a[i]+x);
	return res;
}
poly max(CO poly&a,CO poly&b){
	poly res(max(a.size(),b.size()));
	for(int i=0;i<(int)res.size();++i)
		res[i]=max(i<(int)a.size()?a[i]:-inf,i<(int)b.size()?b[i]:-inf);
	return res;
}

void merge_list(int u){
	vector<int> s;
	for(;u;u=son[u]) s.push_back(u);
	for(int i=0;i<(int)s.size();++i)for(int c=0;c<=1;++c){
		g[c][c][i]=f[c][s[i]];
		g[c][c^1][i]={-inf};
	}
	for(int len=2;len/2<=(int)s.size();len<<=1)
		for(int i=0;i+len/2<(int)s.size();i+=len){
			poly res[2][2];
			for(int a=0;a<=1;++a)for(int b=0;b<=1;++b)
				for(int c=0;c<=1;++c)for(int d=0;d<=1;++d){
					poly t=merge(g[a][b][i],g[c][d][i+len/2]);
					res[a][d]=max(res[a][d],t);
					if(!b and !c){
						int _a=a,_d=d;
						if(len/2==1) _a=1;
						if(len/2==1 or i+len/2==(int)s.size()-1) _d=1;
						t=shift(t,val[s[i+len/2]]);
						res[_a][_d]=max(res[_a][_d],t);
					}
				}
			for(int a=0;a<=1;++a)for(int b=0;b<=1;++b)
				g[a][b][i]=res[a][b];
		}
}

poly h[2][N];

void merge_son(int u){
	vector<int> s;
	for(CO edge&e:to[u])if(e.v!=fa[u] and e.v!=son[u])
		s.push_back(e.v);
	
	h[0][0]={0},h[1][0]={-inf};
	for(int i=0;i<(int)s.size();++i){
		merge_list(s[i]);
		h[0][i]={0},h[1][i]={-inf};
		for(int a=0;a<=1;++a)for(int b=0;b<=1;++b){
			h[0][i]=max(h[0][i],g[a][b][0]);
			if(!a) h[1][i]=max(h[1][i],shift(g[a][b][0],val[s[i]]));
		}
	}
	
	for(int len=2;len/2<=(int)s.size();len<<=1)
		for(int i=0;i+len/2<(int)s.size();i+=len){
			poly res[2];
			for(int a=0;a<=1;++a)for(int b=0;b<=1;++b){
				if(a and b) continue;
				poly t=merge(h[a][i],h[b][i+len/2]);
				res[a|b]=max(res[a|b],t);
			}
			for(int c=0;c<=1;++c) h[c][i]=res[c];
		}
		
	for(int c=0;c<=1;++c) f[c][u]=h[c][0];
}

void dfs(int u){
	siz[u]=1;
	for(CO edge&e:to[u])if(e.v!=fa[u]){
		fa[e.v]=u;
		dfs(e.v);
		siz[u]+=siz[e.v];
		fa[e.v]=u,val[e.v]=e.w;
		if(siz[e.v]>siz[son[u]]) son[u]=e.v;
	}
	merge_son(u);
}

int main(){
	int n=read<int>();
	for(int i=1;i<n;++i){
		int u=read<int>(),v=read<int>(),w=read<int>();
		to[u].push_back({v,w}),to[v].push_back({u,w});
	}
	dfs(1);
	merge_list(1);
	poly res;
	for(int a=0;a<=1;++a)for(int b=0;b<=1;++b)
		res=max(res,g[a][b][0]);
	for(int i=1;i<n;++i){
		if(i<(int)res.size()) printf("%lld",res[i]);
		else putchar('?');
		putchar(" 
"[i==n-1]);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/autoint/p/12302969.html