题目大意: 给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;
}