CF_101853B New Assignment (二分图+基础数论+仔细读题)

19 篇文章 0 订阅
8 篇文章 0 订阅

题目大意: 给n个人分组,这n个人格子有一个权值,分组条件是,组内人数不大于2人,必须异性,且gcd > 1。问最少分几组,对于所有的输入数据有: n <= 10000,ai <= 1000000,且任意三个人 必定互质(gcd = 1)

显然可以贪心得出尽量多分2人的组可以使组数最少,因为一组最多两人,而且必须异性,gcd必须大于1,很容易看出是一个二分图匹配模型。但是 n 非常大,好像莽不过去?往下看

这题可以用网络流尝试莽(网络流不行的情况下也可以用Hop什么的算法,比网络流更快)。但问题不在于网络流的复杂度,而在于建图。暴力建图复杂度 3e7 * 5lg(1e6) = 9e8,莽不过去。

题目说任意三个人必定互质,说明任意一个素数,最多只能整除2个整数(当时就是没注意读这个),可以先筛一个素数表,建一个以素数为下标的数组容器,然后把人分成男女两组。先在男生一组处理,可以知道每个素数只能整除一个整数,因此我们可以处理出每个素数能整除的男生的编号。这个复杂度大概是 750(1000以内只有750个素数)* 10000 * lg(1e6), 总复杂度大概 4e7 (在相当极限的数据下是这个复杂度,而且总时间有1.5s,完全可以跑1e7 量级的复杂度,况且这个数据完全不需要跑图算法,一般卡人数据应该是 750*lg(1e6) * 5e3,因为这个情况边最多 ),然后在女生组里用同样的处理方法,这时只要找到了对应编号的男生,然后加一条匹配边就行了(详见代码,男女组分组处理)。(会有重边,但很少,可以莽)

因为 n 为 1e4 ,跑不了 匈牙利,但是可以跑网络流。建一个 超级源点,连接所有男生,和一个超级汇点连接所有女生,注意正边权全部为1,反边权为0(男女之间连的边可以建边权为无穷的边,但这里为了方便叙述,都只建边权为1的边)

/*
 	网络流跑二分图最大匹配
 	by : xuzihao
 	data: 2019/4/9 22:19
 	algorithm:dinic,线性筛 
*/
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define inf 0x3f3f3f3f
#define fir first
#define sec second
int t,n;
const int maxn = 1e4+20;
const int maxm = 1e6+10;
int a[maxn],head[maxn],cnt,d[maxn],cur[maxn];
vector<pii> g,h;
int mat[maxm];
struct ss{
 	int v,w,nxt;
}edg[maxn];
void init(){
 	cnt = 0;
 	memset(head,-1,sizeof(head));
 	h.clear();
 	g.clear();
 	memset(mat,0,sizeof(mat));
}
void add(int u,int v,int w){
 	edg[cnt].v = v;
 	edg[cnt].w = w;
 	edg[cnt].nxt = head[u];
 	head[u] = cnt++;
}
bool ispri[maxn];
int pri[maxn];
void sieve(int n){
 	ispri[0] = ispri[1] = true;
 	pri[0] = 0;
 	for(int i = 2; i <= n; i++){
 		if(!ispri[i]) pri[++pri[0]] = i;
 			for(int j = 1; j <= pri[0] && i * pri[j] <= n; j++){
  				ispri[i * pri[j]] = true;
   				if(i % pri[j] == 0) break;
  		}
 	}
}
bool bfs(int s,int t){
 	memset(d,0,sizeof(d));
 	queue<int> pq;
 	pq.push(s);
 	d[s] = 1;
 	while(!pq.empty()){
 		int top = pq.front();
  		pq.pop();
  		for(int i = head[top]; i + 1; i = edg[i].nxt){
  		 	int v = edg[i].v, w = edg[i].w;
   			if(w && !d[v]){
    				d[v] = d[top] + 1;
    				pq.push(v);
   			}
  		}
 	}
 	return d[t];
}
int dfs(int s,int t,int inflow){
 	if(s == t) return inflow;
 	int res = 0;
 	for(int i = head[s]; i + 1; i = edg[i].nxt){
  		int v = edg[i].v,w = edg[i].w;
  		if(w && d[s] + 1 == d[v]){
   			int x = dfs(v,t,min(w,inflow));
   			edg[i].w -= x;
   			inflow -= x;
   			edg[i^1].w += x;
   			res += x;
   			if(!inflow) break;
  		}
 	}
 	if(!res) d[s] = -2;
 		return res;
}
int dinic(int s,int t){
 	int res = 0;
 	while(bfs(s,t)){
 		for(int i = 1; i <= n + 2; i++) cur[i] = head[i];
  			res += dfs(s,t,inf);
 	}
 	return res;
}
char s[2];
int main(){
 	cin >> t;
 	sieve(1000);
 	while(t--){
 	 	scanf("%d",&n);
  		init();
  		for(int i = 1; i <= n; i++)
   			scanf("%d",&a[i]);
  		for(int i = 1; i <= n; i++){
   			scanf("%s",s);
   			if(s[0]=='M') g.push_back(pii(a[i],i));
   			else h.push_back(pii(a[i],i));
  		}
  		for(int i = 0; i < g.size(); i++){		//处理男生组,存储对应素数可以整除的男生的编号
   			for(int j = 1; j <= pri[0] && pri[j] * pri[j] <= g[i].fir; j++){
   	 			if(g[i].fir % pri[j] == 0) mat[pri[j]] = g[i].sec;
    				while(g[i].fir % pri[j] == 0)
     			g[i].fir /= pri[j];
   			}
   			if(g[i].fir > 1) mat[g[i].fir] = g[i].sec;
  		}
  		for(int i = 0; i < h.size(); i++){		//处理女生组,如果可以整除女生的权值的素数里,已有男生,则可以建边(仔细看一遍代码就懂了)
   			for(int j = 1; j <= pri[0] && pri[j] * pri[j] <= h[i].fir; j++){
    				if(h[i].fir % pri[j] == 0 && mat[pri[j]]){
     	 			add(mat[pri[j]],h[i].sec,1);
     				add(h[i].sec,mat[pri[j]],0);
    				}
    				while(h[i].fir % pri[j] == 0)
     				h[i].fir /= pri[j];
   			}
   			if(h[i].fir > 1){
    				if(mat[h[i].fir]){
     				add(mat[h[i].fir],h[i].sec,1);
     				add(h[i].sec,mat[h[i].fir],0);
    				}
   			}
  		}
  		for(int i = 0; i < g.size(); i++){			//超级源点
   			add(n+1,g[i].sec,1);
   			add(g[i].sec,n+1,0);
  		}
  		for(int i = 0; i < h.size(); i++){			//超级汇点
   			add(h[i].sec,n+2,1);
   			add(n+2,h[i].sec,0);
  		}
  		printf("%d\n",n - dinic(n + 1,n + 2));	
 	} 
 	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值