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("\n"); for (i=0;i<=k;i++) for (j=head[i];j!=-1;j=edge[j].next) printf("%d %d %d\n",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\n",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\n",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\n",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\n",v[prime[i]][1],-v[prime[i]][0]); } } int ans=sap(0,n+1,n+2); printf("%d\n",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\n",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\n",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\n",ans); } }
原文地址:https://www.cnblogs.com/nowheretrix/p/9386001.html
时间: 2024-09-27 16:21:52