ACM International Collegiate Programming Contest, JUST Collegiate Programming Contest (2018)
B. New Assignment
- 有n个人(1 ≤ n ≤ 104),有男有女,每个人都有一个id,现在这n个人分成学习互助小组,有三种组队模式,一个男人一组,一个女人一组,一男一女一组,如果要一男一女一组,那么这两人id的gcd要>1。保证任意三个人的gcd=1。求小组的组数最少是多少?
- 看起来是一个很裸的二分匹配,当两个人性别为男女,并且gcd>1时,连边。可是这里的连边的时候复杂度很高。直接暴力连边为5000 × 5000×log级别的。所以,需要换一个角度考虑。
- 可以发现,由于任意三个数的gcd=1的性质,可以保证任何一个质因子最多有两个被除数。当且仅当这两个被除数的性别不同连边。
#include"stdio.h"
#include"string.h"
#include"queue"
#include"vector"
#include"algorithm"
#include"iostream"
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=100010;//点数
const int maxm=400010;//边数
struct node{
int v,next,cap,flow;
}edge[maxm];
int cnt;
int head[maxn];
int cur[maxn],d[maxn];// 当前弧下标 结点到汇点弧长
int p[maxn],gap[maxn];////可增广路上的上一条弧 gap优化
int ss[maxn];//保存路径
void init(){
cnt=-1;
memset(head,-1,sizeof(head));
}
void debug(int k){
int i,j;
printf("
");
for (i=0;i<=k;i++)
for (j=head[i];j!=-1;j=edge[j].next)
printf("%d %d %d
",i,edge[j].v,edge[j].flow);
}
void add(int u,int v,int w,int rw=0){
cnt++;
edge[cnt].v=v;
edge[cnt].next=head[u];
edge[cnt].cap=w;
edge[cnt].flow=0;
head[u]=cnt;
cnt++;
edge[cnt].v=u;
edge[cnt].next=head[v];
edge[cnt].cap=rw;
edge[cnt].flow=0;
head[v]=cnt;
}
void bfs(int k){
int v,i;
int u;
memset(gap,0,sizeof(gap));
memset(d,-1,sizeof(d));
d[k]=0;
gap[0]++;
queue<int> q;
q.push(k);
while(q.empty()==false){
u=q.front();q.pop();
for (i=head[u];i!=-1;i=edge[i].next){
v=edge[i].v;
if (d[v]==-1) {
d[v]=d[u]+1;
gap[d[v]]++;
q.push(v);
}
}
}
}
int sap(int s,int t,int N){
int i;
bfs(t);
memcpy(cur,head,sizeof(head));
int top=0;
int u=s;
int ans=0;
while(d[s]<N){
if (u==t){
int min=inf;
int inser;
for (i=0;i<top;i++){
if (min>=edge[ss[i]].cap-edge[ss[i]].flow){
min=edge[ss[i]].cap-edge[ss[i]].flow;
inser=i;//这跟管子是满流了
}
}
for (i=0;i<top;i++){
edge[ss[i]].flow+=min;
edge[ss[i]^1].flow-=min;
}
ans+=min;
top=inser;
u=edge[ss[top]^1].v;//u为满流的那个结点 也就是那根管子的倒管子的终点
continue;
}
bool ok=false;
int v;
for (i=cur[u];i!=-1;i=edge[i].next){
v=edge[i].v;
if (edge[i].cap-edge[i].flow>0&&d[v]+1==d[u]){
ok=true;
cur[u]=i;
break;
}//u后 添加了一条下标为i的弧
}
if(ok==true){
ss[top]=cur[u];
top++;
u=v;
continue;
}
//如果这条路已经走不通了,那么撤退
int min=N;
for (i=head[u];i!=-1;i=edge[i].next)
if (edge[i].cap-edge[i].flow>0&&d[edge[i].v]<min){
min=d[edge[i].v];
cur[u]=i;
}
gap[d[u]]--;
if (gap[d[u]]==0) return ans;
d[u]=min+1;
gap[d[u]]++;
if (u!=s) {
top--;
u=edge[ss[top]^1].v;
}
}
return ans;
}
int gcd(int a,int b){
if (a%b==0) return b;
return gcd(b,a%b);
}
int prime[600000]={0},numprime=0;
bool isNotPrime[1000010]={1,1};
void sushu(int N){
long long int i;
for (i=2;i<=N;i++) {
if(! isNotPrime[i])
prime[numprime ++]=i;
for(long j = 0 ; j < numprime && i * prime[j] < N ; j ++)
{
isNotPrime[i * prime[j]] = 1;
if( !(i % prime[j] ) )
break;
}
}
}
int a[10500];
char s[10];
vector<int > v[1000005];
int vis[1000005];
int main(){
int e,t,i,j,x,y,cap,now;
int n,m;
sushu(1000000);
scanf("%d",&t);
for (e=1;e<=t;e++){
memset(vis,0,sizeof(vis));
for (i=0;i<numprime;i++) v[prime[i]].clear();
init();
scanf("%d",&n);
for (i=1;i<=n;i++) scanf("%d",&a[i]);
for (j=1;j<=n;j++) {
scanf("%s",s);
if (s[0]=='M') {
now=a[j];
for (i=0;i<numprime&&prime[i]<=1000;i++) {
// printf("%d
",now);
if (now%prime[i]==0) v[prime[i]].push_back(j);
while (now%prime[i]==0) now=now/prime[i];
if (now==1||isNotPrime[now]==0) break;
}
if (isNotPrime[now]==0) v[now].push_back(j);
} else {
now=a[j];
for (i=0;i<numprime&&prime[i]<=1000;i++) {
if (now%prime[i]==0) v[prime[i]].push_back(-j);
while (now%prime[i]==0) now=now/prime[i];
if (now==1||isNotPrime[now]==0) break;
}
if (isNotPrime[now]==0) v[now].push_back(-j);
}
}
// printf("%d %d %d
",prime[0],v[prime[0]][0],v[prime[0]][1]);
for (i=0;i<numprime;i++)
if (v[prime[i]].size()==2&&v[prime[i]][0]*v[prime[i]][1]<0) {
if (v[prime[i]][0]>0) {
if (vis[v[prime[i]][0]]==0) {add(0,v[prime[i]][0],1);vis[v[prime[i]][0]]=1;}
if (vis[-v[prime[i]][1]]==0) {add(-v[prime[i]][1],n+1,1);vis[-v[prime[i]][1]]=1;}
add(v[prime[i]][0],-v[prime[i]][1],inf);
// printf("%d %d
",v[prime[i]][0],-v[prime[i]][1]);
}
else {
if (vis[v[prime[i]][1]]==0) {add(0,v[prime[i]][1],1);vis[v[prime[i]][1]]=1;}
if (vis[-v[prime[i]][0]]==0) {add(-v[prime[i]][0],n+1,1);vis[-v[prime[i]][0]]=1;}
add(v[prime[i]][1],-v[prime[i]][0],inf);
// printf("%d %d
",v[prime[i]][1],-v[prime[i]][0]);
}
}
int ans=sap(0,n+1,n+2);
printf("%d
",n-ans);
}
}
E. Maximum Sum
- 取数问题。16*16的矩阵,如果你取了这个数,那么周围8个格子的数都不能取。求取的数和最大。
- dp瞎搞。事先筛选出行内任意两个相邻位置不同的状态,大约有3000种不到。
- dp[i][j]代表第i行,这一行的取数方案为j时,前i行的最大和。由于第i-1行无法去影响i+1行,故可以这样设置状态。
- 考虑转移, 设上一行的状态为k,当前行的状态为j,如果j and k==0 并且 j<<1 and k ==0 并且 j and k<<1 ==0 那么表示可以转移。
- 尽管算下来是超高的复杂度,不过千万不要低估银河评测机的实力。
#include"stdio.h"
#include"string.h"
#include"vector"
#include"algorithm"
using namespace std;
vector<int> v;
int d[20];
int a[50][50];
int dp[17][2600];
int main(){
int i,j,k,l;
int e,t,n;
int ans,x;
int tmp;
d[0]=1;
v.clear();
for (i=1;i<=17;i++) d[i]=d[i-1]*2;
for (i=0;i<=d[16]-1;i++) {
int sign=0;
for (j=0;j<=14;j++)
if ((i&d[j])>0&&(i&d[j+1])>0) {sign=1;break;}
if (sign==0) v.push_back(i);
}
l=v.size();
scanf("%d",&t);
for (e=1;e<=t;e++) {
scanf("%d",&n);
for (i=1;i<=n;i++)
for (j=1;j<=n;j++) scanf("%d",&a[i][j]);
memset(dp,0,sizeof(dp));
for (i=0;i<l&&v[i]<=d[n]-1;i++) {
for (j=1;j<=n;j++) if ((v[i]&d[j-1])>0) dp[1][i]+=a[1][j];
// printf("%d :%d
",i,dp[1][v[i]]);
}
for (k=2;k<=n;k++) {
for (i=0;i<l&&v[i]<=d[n]-1;i++) {
int tmp=0;
for (x=1;x<=n;x++) if ((v[i]&d[x-1])>0) tmp+=a[k][x];
for (j=0;j<l&&v[j]<=d[n]-1;j++)
if ((v[i]&v[j])==0&&((v[i]<<1)&v[j])==0&&(v[i]&(v[j]<<1))==0) {
dp[k][i]=max(dp[k][i],tmp+dp[k-1][j]);
// printf("%d :%d :%d
",k,v[i],dp[k][v[i]]);
}
}
}
ans=0;
for (i=0;i<l&&v[i]<=d[n]-1;i++) ans=max(dp[n][i],ans);
printf("%d
",ans);
}
}