zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    Photoshop 基础三 制作简单按钮
    Photoshop 基础二 快捷键
    Photoshop 基础一 安装
    Winform 基础二 最小化 最大化 关闭 点击任务栏隐藏显示 点击鼠标左键移动窗体
    Winform 基础一 panel
    ASP 基础三 SQL指令
    回文&升降 数位dp
    双向链表()
    Two strings HDU
    Just do it HDU
  • 原文地址:https://www.cnblogs.com/autoint/p/12302969.html
Copyright © 2011-2022 走看看