【KM算法】【最大乘积生成树】[HNOI2014] bzoj3571 画框

题目点这里


这道题考试的时候我直接写了个KM拿30分就滚去写下一题了orz。。

看了题解感觉想出来的人脑洞真的…………比较大………………


把每一种匹配 (sigma(A), sigma(B))看做平面上的一个点

因为要求乘积最小 可以证明这个点肯定在下凸壳上 于是把下凸壳上的每个点逐一验证就可以了

构造下凸壳用的分治_(:з)∠)_ 

首先纵坐标最大的和横坐标最小的肯定在下凸壳上

然后找到一个点距离它们连成的直线距离最远 这个点肯定也在下凸壳上

然后再把这个点和原来的两个点构成新的直线再找最远的点直到找不到就行了

找最远的时候拿数学公式YY一下就行了 _(:з)∠)_


10组数据4000多ms感觉基本是卡过去的。。不知道是不是因为pair有常数?

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

int read()
{
	int n = 0, sign = 1; char c = getchar();
	while(c > '9' || c < '0') {if(c == '-') sign = -1; c = getchar();}
	while(c >= '0' && c <= '9') {n = n*10 + c-'0'; c = getchar(); }
	return sign * n;
}

typedef pair<int, int> point;

const int inf = 0x3f3f3f3f;

int T, N;
int A[75][75], B[75][75], w[75][75], match[75];
int lx[75], ly[75], slack[75];
bool visx[75], visy[75];

bool dfs(int u)
{
	visx[u] = true;
	for(int v = 1; v <= N; ++v)
	{
		if(visy[v] == true) continue;
		int t = lx[u] + ly[v] - w[u][v];
		if(t == 0)
		{
			visy[v] = true;
			if(match[v] == -1 || dfs(match[v]))
			{
				match[v] = u;
				return true;
			}
		}
		else if(slack[v] > t) slack[v] = t;
	}
	return false;
}

inline void adjust()
{
	int d = inf;
	for(int i = 1; i <= N; ++i) if(!visy[i] && d > slack[i]) d = slack[i];
	for(int i = 1; i <= N; ++i) if(visx[i]) lx[i] -= d;
	for(int i = 1; i <= N; ++i) if(visy[i]) ly[i] += d; else slack[i] -= d;
}

inline point KM()
{
	memset(match, -1, sizeof(match));
	memset(ly, 0, sizeof(ly));
	for(int i = 1; i <= N; ++i)
	{
		lx[i] = -inf;
		for(int j = 1; j <= N; ++j) if(w[i][j] > lx[i]) lx[i] = w[i][j];
	}
		
	for(int i = 1; i <= N; ++i)
	{
		for(int j = 1; j <= N; ++j) slack[j] = inf;
		for( ; ; )
		{
			memset(visx, 0, sizeof(visx));
			memset(visy, 0, sizeof(visy));
			if(dfs(i)) break;
			else adjust();
		}
	}
	
	int temp1 = 0, temp2 = 0;
	for(int i = 1; i <= N; ++i) if(match[i] != -1)
	{
		temp1 += A[match[i]][i];
		temp2 += B[match[i]][i];
	}
	return point(temp1, temp2);
}

int find(point P, point Q)
{
	for(int i = 1; i <= N; ++i)
		for(int j = 1; j <= N; ++j)
			w[i][j] = A[i][j] * (Q.second - P.second) + B[i][j] * (P.first - Q.first);
	point temp = KM();
	if(temp == P || temp == Q) return min(P.first * P.second, Q.first * Q.second);
	return min(find(P, temp), find(temp, Q));
}

int main()
{
	freopen("frame.in", "r", stdin);
	freopen("frame.out", "w", stdout);
	
	for(T = read(); T--; )
	{
		N = read(); 
		for(int i = 1; i <= N; ++i) 
			for(int j = 1; j <= N; ++j) 
				{ A[i][j] = read(); w[i][j] = -A[i][j]; }
		for(int i = 1; i <= N; ++i) 
			for(int j = 1; j <= N; ++j) 
				B[i][j] = read();
			
		point temp1 = KM();
		for(int i = 1; i <= N; ++i) 
			for(int j = 1; j <= N; ++j) 
				w[i][j] = B[i][j];
		point temp2 = KM();
		
		printf("%d\n", find(temp1, temp2));
	}
	
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值