[费用流]数字配对,新生舞会

T1:数字配对

题目

有 n 种数字,第 i 种数字是 ai、有 bi 个,权值是 ci。
若两个数字 ai、aj 满足,ai 是 aj 的倍数,且 ai/aj 是一个质数,
那么这两个数字可以配对,并获得 ci×cj 的价值。
一个数字只能参与一次配对,可以不参与配对。
在获得的价值总和不小于 0 的前提下,求最多进行多少次配对

Input
第一行一个整数 n。
第二行 n 个整数 a1、a2、……、an。
第三行 n 个整数 b1、b2、……、bn。
第四行 n 个整数 c1、c2、……、cn。
Output
一行一个数,最多进行多少次配对

Sample Input
3
2 4 8
2 200 7
-1 -2 1
Sample Output
4

Hint
n 200 a i 1 0 9 b i 1 0 5 c i 1 0 5 n≤200,ai≤10^9,bi≤10^5,∣ci∣≤10^5

题解

首先我们考虑如何判断 a i / a j ai/aj 为质数,肯定的我们把 a i ai a j aj 进行标准的唯一质因数分解
a j aj 的所有因数 a i ai 都有,并且 a j aj 的每一个因数的幂 该因数在 a i ai 中的幂,这样才能保证 a i % a j = = 0 ai\%aj==0

那么接下来就判断 a i / a j ai/aj 等于一个质数,其实就是对于一个质因数 x x ,设 x x a j aj 中的幂为 m 1 m1 ,在 a i ai 中的幂为 m 2 m2 满足 m 1 = = m 2 m 1 = = m 2 1 m1==m2||m1==m2-1 且只会有一个质因数在 a j aj 的幂小于 a i ai 中的幂其他的都是等于,不然两个质因数乘起来还是一个合数


但是我们在建图时会遇到一个问题,如果每一个点都与超级源点和超级汇点建边,有可能直接就只流了该点与源点的边马上就流回了汇点,并没有与其他点产生关系

那么我们为了避免这个问题,就抓住 a i / a j ai/aj 为一个质数的特殊性,我们发现 a i ai 的所有质因数的幂的和一定等于 a j aj 的所有质因数的幂的和 + 1 +1
在这里插入图片描述
所以如果两个数的质因数的幂的和都是奇数或者都是偶数,那么两数之间一定是不会满足关系的,和至少都差了2,那么我们就把奇数划分成一个区域,与源点建边,流量就是个数,偶数划分成一个区域与汇点建边,流量也是个数,奇数和偶数之间判断幂的和是否只差1,满足的就建边,费用就是能获得的贡献,流量就流无限

CODE

#include <queue>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
#define INF 1e9
#define int long long 
#define MAXN 500
struct node {
	int v, w, next, c, flow;
}edge[MAXN * MAXN];
queue < int > q;
int cnt, n, m, s, t;
int head[MAXN], dis[MAXN], pre[MAXN], a[MAXN], v[MAXN], tot[MAXN];
bool vis[MAXN];

void add ( int x, int y, int flow, int cost ) {
	edge[cnt].next = head[x];
	edge[cnt].v = y;
	edge[cnt].w = cost;
	edge[cnt].c = flow;
	edge[cnt].flow = 0;
	head[x] = cnt ++;	
}

bool spfa () {
	memset ( vis, 0, sizeof ( vis ) ); 
	memset ( dis, -0x7f, sizeof ( dis ) );
	memset ( pre, -1, sizeof ( pre ) );
	while ( ! q.empty() )
		q.pop();
	q.push( s );
	dis[s] = 0;
	vis[s] = 1;
	while ( ! q.empty() ) {
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for ( int i = head[u];~ i;i = edge[i].next ) {
			int v = edge[i].v;
			if ( dis[v] < dis[u] + edge[i].w && edge[i].c > edge[i].flow ) {
				dis[v] = dis[u] + edge[i].w;
				pre[v] = i;
				if ( ! vis[v] ) {
					q.push( v );
					vis[v] = 1;
				}
			}
		}
	} 
	return pre[t] != -1;
}

int Fabs ( int x ) {
	if ( x < 0 )
		return -x;
	return x;
}

void MCMF ( int &maxflow, int &mincost ) {
	maxflow = mincost = 0;
	while ( spfa() ) {
		int MIN = INF;
		for ( int i = pre[t];~ i;i = pre[edge[i ^ 1].v] )
			MIN = min ( MIN, edge[i].c - edge[i].flow );
		if ( mincost + MIN * dis[t] < 0 ) {//不能流完就尽量流,因为这个delta是单峰,一旦下降便不会在上升 
			maxflow += ( mincost / -dis[t] );
			break;
		}
		maxflow += MIN;
		for ( int i = pre[t];~ i;i = pre[edge[i ^ 1].v] ) {
			edge[i].flow += MIN;
			edge[i ^ 1].flow -= MIN;
			mincost += MIN * edge[i].w;
		}
	}
}

signed main() {
	memset ( head, -1, sizeof ( head ) );
	scanf ( "%lld", &n );
	for ( int i = 1;i <= n;i ++ ) {
		scanf ( "%lld", &a[i] );
		int x = a[i];
		for ( int j = 2;j * j <= x;j ++)
			while ( x % j == 0 ) {
				x /= j;
				tot[i] ++;
			}
		if ( x != 1 )
			tot[i] ++;
	}
	s = 0;t = n + 1;
	for ( int i = 1;i <= n;i ++ ) {
		int w;
		scanf ( "%lld", &w );
		if ( tot[i] & 1 ) {
			add ( s, i, w, 0 );
			add ( i, s, 0, 0 );
		}
		else {
			add ( i, t, w, 0 );
			add ( t, i, 0, 0 );
		}
	}
	for ( int i = 1;i <= n;i ++ )
		scanf ( "%lld", &v[i] );
	for ( int i = 1;i <= n;i ++ ) {
		if ( tot[i] & 1 ) {
			for ( int j = 1;j <= n;j ++ )
				if ( Fabs ( tot[i] - tot[j] ) == 1 && ( a[i] % a[j] == 0 || a[j] % a[i] == 0 ) ) {
					add ( i, j, INF, v[i] * v[j] );
					add ( j, i, 0, - ( v[i] * v[j] ) );
				}
		}
	}
	int maxflow, mincost;
	MCMF ( maxflow, mincost );
	printf ( "%lld", maxflow );
	return 0;
} 

T2:新生舞会

题目

学校组织了一次新生舞会,Cathy作为经验丰富的老学姐,负责为同学们安排舞伴。有n个男生和n个女生参加舞会
买一个男生和一个女生一起跳舞,互为舞伴。Cathy收集了这些同学之间的关系,比如两个人之前认识没计算得出 a[i][j] ,表示第i个男生和第j个女生一起跳舞时他们的喜悦程度。Cathy还需要考虑两个人一起跳舞是否方便,比如身高体重差别会不会太大,计算得出 b[i][j],表示第i个男生和第j个女生一起跳舞时的不协调程度。
当然,还需要考虑很多其他问题。Cathy想先用一个程序通过a[i][j]和b[i][j]求出一种方案,再手动对方案进行微调。Cathy找到你,希望你帮她写那个程序。一个方案中有n对舞伴,假设没对舞伴的喜悦程度分别是a’1,a’2,…,a’n,
假设每对舞伴的不协调程度分别是b’1,b’2,…,b’n。令
C=(a’1+a’2+…+a’n)/(b’1+b’2+…+b’n),Cathy希望C值最大

Input
第一行一个整数n。
接下来n行,每行n个整数,第i行第j个数表示a[i][j]。
接下来n行,每行n个整数,第i行第j个数表示b[i][j]。
1<=n<=100,1<=a[i][j],b[i][j]<=10^4
Output
一行一个数,表示C的最大值。四舍五入保留6位小数,选手输出的小数需要与标准输出相等

Sample Input
3
19 17 16
25 24 23
35 36 31
9 5 6
3 4 2
7 8 9
Sample Output
5.357143

题解

转化一下答案,即
C = i = 1 n a i i = 1 n b i C=\frac{\sum_{i=1}^n a_i}{\sum_{i=1}^{n}b_i}
–>
C i = 1 n b i = i = 1 n a i C\sum_{i=1}^nb_i=\sum_{i=1}^na_i
–>
i = 1 n a i C i = 1 n b i = 0 \sum_{i=1}^na_i-C\sum_{i=1}^nb_i=0
所以我们可以考虑二分答案 C C ,判断 i = 1 n a i C i = 1 n b i 0 \sum_{i=1}^na_i-C\sum_{i=1}^nb_i≥0
男生与超级源点建边,女生与超级汇点建边,男女生之间的建边就是 a i j x b i j a_{ij}-xb_{ij} 的费用
跑一个最大费用最大流

分享另一个想法:我们可以将以上的思路全部去一个反,就变成判断 i = 1 n a i C i = 1 n b i < 0 \sum_{i=1}^na_i-C\sum_{i=1}^nb_i<0 那就是跑最小费用最大流

仙女都给了代码,
在这里插入图片描述

CODE(最大费用最大流版)

#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;
#define eps 1e-7
#define INF 2e9
#define MAXM 200005
#define MAXN 105
queue < int > q;
int cnt, n, s, t;
int head[MAXN * 10], pre[MAXN * 10];
double dis[MAXM], w[MAXM], c[MAXM], flow[MAXM];
int a[MAXN][MAXN], b[MAXN][MAXN], v[MAXM], nxt[MAXM];
bool vis[MAXM];

void add ( int x, int y, double flow_, double cost ) {
	nxt[cnt] = head[x];
	v[cnt] = y;
	w[cnt] = cost;
	c[cnt] = flow_;
	flow[cnt] = 0;
	head[x] = cnt ++;	
}

bool spfa () {
	for ( int i = s;i <= t;i ++ ) {
		dis[i] = -INF;
		pre[i] = -1;
	}
	q.push( s );
	dis[s] = 0;
	vis[s] = 1;
	while ( ! q.empty() ) {
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for ( int i = head[u];~ i;i = nxt[i] ) {
			if ( dis[v[i]] < dis[u] + w[i] && c[i] > flow[i] ) {
				dis[v[i]] = dis[u] + w[i];
				pre[v[i]] = i;
				if ( ! vis[v[i]] ) {
					q.push( v[i] );
					vis[v[i]] = 1;
				}
			}
		}
	}
	return pre[t] != -1;
}

bool MCMF () {
	double mincost = 0;
	while ( spfa() ) {
		for ( int i = pre[t];~ i;i = pre[v[i ^ 1]] ) {
			flow[i] ++;
			flow[i ^ 1] --;
		}
		mincost += dis[t];
	}
	return mincost >= 0;
}

int main() {
	scanf ( "%d", &n );
	s = 0;t = n << 1 | 1;
	double sum = 0;
	for ( int i = 1;i <= n;i ++ )
		for ( int j = 1;j <= n;j ++ ) {
			scanf ( "%d", &a[i][j] ); 
			sum += a[i][j];
		}
	for ( int i = 1;i <= n;i ++ )
		for ( int j = 1;j <= n;j ++ )
			scanf ( "%d", &b[i][j] );
	double l = 0, r = sum;
	while ( r - l > eps ) {
		double mid = ( l + r ) / 2;
		memset ( head, -1, sizeof ( head ) );
		cnt = 0;
		for ( int i = 1;i <= n;i ++ ) {
			add ( s, i, 1, 0 );
			add ( i, s, 0, 0 );
			add ( i + n, t, 1, 0 );
			add ( t, i + n, 0, 0 );
		}
		for ( int i = 1;i <= n;i ++ )
			for ( int j = 1;j <= n;j ++ ) {
				add ( i, j + n, 1, a[i][j] * 1.0 - 1.0 * b[i][j] * mid );
				add ( j + n, i, 0, - ( a[i][j] * 1.0 - 1.0 * b[i][j] * mid ) );
			}
		if ( MCMF() )
			l = mid;
		else
			r = mid;
	}
	printf ( "%.6f", l );
	return 0;
} 

CODE(最小费用最大流版)

#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;
#define eps 1e-7
#define INF 2e9
#define MAXM 200005
#define MAXN 105
queue < int > q;
int cnt, n, s, t;
int head[MAXN * 10], pre[MAXN * 10];
double dis[MAXM], w[MAXM], c[MAXM], flow[MAXM];
int a[MAXN][MAXN], b[MAXN][MAXN], v[MAXM], nxt[MAXM];
bool vis[MAXM];

void add ( int x, int y, double flow_, double cost ) {
	nxt[cnt] = head[x];
	v[cnt] = y;
	w[cnt] = cost;
	c[cnt] = flow_;
	flow[cnt] = 0;
	head[x] = cnt ++;	
}

bool spfa () {
	for ( int i = s;i <= t;i ++ ) {
		dis[i] = INF;
		pre[i] = -1;
	}
	q.push( s );
	dis[s] = 0;
	vis[s] = 1;
	while ( ! q.empty() ) {
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for ( int i = head[u];~ i;i = nxt[i] ) {
			if ( dis[v[i]] > dis[u] + w[i] && c[i] > flow[i] ) {
				dis[v[i]] = dis[u] + w[i];
				pre[v[i]] = i;
				if ( ! vis[v[i]] ) {
					q.push( v[i] );
					vis[v[i]] = 1;
				}
			}
		}
	}
	return pre[t] != -1;
}

bool MCMF () {
	double mincost = 0;
	while ( spfa() ) {
		for ( int i = pre[t];~ i;i = pre[v[i ^ 1]] ) {
			flow[i] ++;
			flow[i ^ 1] --;
		}
		mincost += dis[t];
	}
	return mincost < 0;
}

int main() {
	scanf ( "%d", &n );
	s = 0;t = n << 1 | 1;
	double sum = 0;
	for ( int i = 1;i <= n;i ++ )
		for ( int j = 1;j <= n;j ++ ) {
			scanf ( "%d", &a[i][j] ); 
			sum += a[i][j];
		}
	for ( int i = 1;i <= n;i ++ )
		for ( int j = 1;j <= n;j ++ )
			scanf ( "%d", &b[i][j] );
	double l = 0, r = sum;
	while ( r - l > eps ) {
		double mid = ( l + r ) / 2;
		memset ( head, -1, sizeof ( head ) );
		cnt = 0;
		for ( int i = 1;i <= n;i ++ ) {
			add ( s, i, 1, 0 );
			add ( i, s, 0, 0 );
			add ( i + n, t, 1, 0 );
			add ( t, i + n, 0, 0 );
		}
		for ( int i = 1;i <= n;i ++ )
			for ( int j = 1;j <= n;j ++ ) {
				add ( i, j + n, 1, - ( a[i][j] * 1.0 - 1.0 * b[i][j] * mid ) );
				add ( j + n, i, 0, a[i][j] * 1.0 - 1.0 * b[i][j] * mid );
			}
		if ( MCMF() )
			l = mid;
		else
			r = mid;
	}
	printf ( "%.6f", l );
	return 0;
} 

在这里插入图片描述
byebye,点个赞

猜你喜欢

转载自blog.csdn.net/Emm_Titan/article/details/103564747