[TJOI2011] 卡片(网络流 + 质因子优化建图)

problem

luogu-P2065

solution

这个拿走一组共两张卡片的操作其实就是一个匹配。

直接两个数的最大公约数大于 1 1 1 就建一条边,跑二分图匹配最大流即可。

然而如果直接枚举两个数然后算他们的 gcd \text{gcd} gcd ,时间复杂度 O ( T n 2 log ⁡ V ) O(Tn^2\log V) O(Tn2logV) TLE \text{TLE} TLE

又不能预处理任意两个数的 gcd \text{gcd} gcd,这个数字 ∈ ( 1 , 1 e 7 ) \in(1,1e7) (1,1e7) 实在是太大了。

这里很巧妙,任何数都可以拆成若干个质数的幂,两个数的 gcd > 1 \text{gcd}>1 gcd>1 无非就是两个数有相同的某些个质因子。

所以我们可以预处理出 1 e 7 1e7 1e7 以内的所有质数,这并不多,然后将一个数和其质因子连边。

只显然每张卡片只能拿走一次,流量与源汇点之间为 1 1 1 即可。

这样时间复杂度就是 O ( T n n ) O(Tn\sqrt{n}) O(Tnn ) ,网络流就没考虑反正是 O ( O( O( 能过 ) ) )

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
#define maxm 800005
int T, m, n, s, t, cnt = -1, tot;
struct node { int to, nxt, flow; }E[maxm];
int b[maxn], r[maxn], head[maxn], cur[maxn], dep[maxn];
queue < int > q;

int gcd( int x, int y ) {
	if( ! y ) return x;
	else return gcd( y, x % y );
}

void addedge( int u, int v, int w ) {
	E[++ cnt] = { v, head[u], w }, head[u] = cnt;
	E[++ cnt] = { u, head[v], 0 }, head[v] = cnt;
}

bool bfs() {
	memset( dep, 0, sizeof( dep ) );
	memcpy( cur, head, sizeof( head ) );
	dep[s] = 1; q.push( s );
	while( ! q.empty() ) {
		int u = q.front(); q.pop();
		for( int i = head[u];~ i;i = E[i].nxt ) {
			int v = E[i].to;
			if( ! dep[v] and E[i].flow ) {
				dep[v] = dep[u] + 1;
				q.push( v );
			}
		}
	}
	return dep[t];
}

int dfs( int u, int cap ) {
	if( u == t or ! cap ) return cap;
	int flow = 0;
	for( int i = cur[u];~ i;i = E[i].nxt ) {
		cur[u] = i; int v = E[i].to;
		if( dep[v] == dep[u] + 1 ) {
			int w = dfs( v, min( cap, E[i].flow ) );
			if( ! w ) continue;
			E[i ^ 1].flow += w;
			E[i].flow -= w;
			flow += w;
			cap -= w;
			if( ! cap ) break;
		}
	}
	return flow;
}

int dinic() {
	int ans = 0;
	while( bfs() ) ans += dfs( s, 1e9 );
	return ans;
}

#define maxp 10000005
int prime[maxp], vis[maxp], num[maxp];
int cntp;
void sieve() {
	for( int i = 2;i < 1e7;i ++ ) {
		if( ! vis[i] ) prime[++ cntp] = i;
		for( int j = 1;j <= cntp and i * prime[j] < 1e7;j ++ ) {
			vis[i * prime[j]] = 1;
			if( i % prime[j] == 0 ) break;
		}
	}
}
void divide( int id, int val ) {
	for( int i = 1;i <= cntp and prime[i] <= val;i ++ ) 
		if( val % prime[i] == 0 ) {
			if( ! num[prime[i]] ) num[prime[i]] = ++ tot;
			if( id <= m ) addedge( id, num[prime[i]], 1e9 );
			else addedge( num[prime[i]], id, 1e9 );
			while( val % prime[i] == 0 ) val /= prime[i];
		}
}


int main() {
	sieve(); tot = 1001;
	scanf( "%d", &T );
	while( T -- ) {
		memset( head, -1, sizeof( head ) ); cnt = -1;
		scanf( "%d %d", &m, &n );
		s = 0, t = n + m + 1;
		for( int i = 1;i <= m;i ++ ) scanf( "%d", &b[i] );
		for( int i = 1;i <= n;i ++ ) scanf( "%d", &r[i] );
		for( int i = 1;i <= m;i ++ ) divide( i, b[i] );
		for( int i = 1;i <= n;i ++ ) divide( i + m, r[i] );
		for( int i = 1;i <= m;i ++ ) addedge( s, i, 1 );
		for( int i = 1;i <= n;i ++ ) addedge( i + m, t, 1 );
		printf( "%d\n", dinic() );
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值