NPC,即Non_Player Character,作为游戏很重要的一种存在……
哎不对,扯远了。
这题出题人卖萌,明显不是NPC问题。
我们可以发现(通过前几个点找一找规律什么的)这题可以建立一个一般图最大匹配模型。
首先将所有的筐子拆成3个点,任选其中两点连边,然后对于每一个条件,将对应球与筐子的三个点分别连边。
可以证明(不会),最大匹配中所有球一定是匹配了的。
于是就可以用带花树(没学过)求解一般图最大匹配了。
复杂度O(VE),和二分图最大匹配一样
代码写挫了,跑了78MS。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=600+5; struct Edge{int to,next;}e[N*N]; int head[N],cnt; void ins(int u,int v){ e[++cnt]=(Edge){v,head[u]};head[u]=cnt; } void insert(int u,int v){ ins(u,v);ins(v,u); } int pa[N]; int find(int x){ return pa[x]==x?x:pa[x]=find(pa[x]); } void merge(int u,int v){ u=find(u);v=find(v); if(u!=v)pa[u]=v; } int n,linked[N],next[N],que[N],front,rear,mark[N],vis[N]; int lca(int u,int v){ static int T=0;T++; while(true){ while(u!=-1){ u=find(u); if(vis[u]==T)return u; vis[u]=T; if(linked[u]!=-1)u=next[linked[u]]; else u=-1; } swap(u,v); } } void blossom(int a,int t){ while(a!=t){ int b=linked[a],c=next[b]; if(find(c)!=t)next[c]=b; if(mark[b]==2)mark[que[++rear]=b]=1; if(mark[c]==2)mark[que[++rear]=c]=1; merge(a,b);merge(b,c); a=c; } } void aug(int s){ for(int i=1;i<=n;i++) next[i]=vis[i]=-1,mark[i]=0,pa[i]=i; mark[s]=1;que[rear=0]=s; for(front=0;linked[s]==-1&&front<=rear;front++){ int u=que[front]; for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(linked[u]==v||find(u)==find(v)||mark[v]==2)continue; if(mark[v]==1){ int w=lca(u,v); if(find(u)!=w)next[u]=v; if(find(v)!=w)next[v]=u; blossom(u,w); blossom(v,w); }else if(linked[v]==-1){ next[v]=u; for(u=v;u!=-1;){ v=next[u]; int tmp=linked[v]; linked[u]=v;linked[v]=u; u=tmp; } break; }else{ next[v]=u; mark[que[++rear]=linked[v]]=1; mark[v]=2; } } } } int sz,node[105][3],belong[N]; int main(){ int cas;scanf("%d",&cas); while(cas--){ int a,b,c;scanf("%d%d%d",&a,&b,&c); memset(head,0,sizeof(head));cnt=0; sz=a; for(int i=1;i<=b;i++){ node[i][0]=++sz;belong[sz]=i; node[i][1]=++sz;belong[sz]=i; node[i][2]=++sz;belong[sz]=i; insert(node[i][0],node[i][1]); } n=sz; for(int i=1;i<=c;i++){ int u,v;scanf("%d%d",&u,&v); for(int j=0;j<3;j++) insert(u,node[v][j]); } memset(linked,-1,sizeof(linked)); for(int i=1;i<=n;i++) if(linked[i]==-1)aug(i); int ans=0; for(int i=1;i<=n;i++)if(linked[i]!=-1)ans++; printf("%d\n",ans/2-a); for(int i=1;i<=a;i++) printf("%d ",belong[linked[i]]); putchar('\n'); } return 0; }
才过了一个月发现带花树这东西感觉跟没学过一样啊TAT,蒟蒻表示有点方。
然而这题总不能就这么不会了吧,于是从论文里学了一种特殊的玩泥巴技巧
大概就是随机化+匈牙利,最好战绩水到了80分QAQ,果然数据是特殊构造的。。。。
有两个点T了,感觉时间宽一点大概就A了吧。
顺便说这个代码A掉了模板题库里的一般图最大匹配,这时候应该有老司机来hack我。。。。
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<ctime> using namespace std; const int N=600+5; bool g[N][N]; bool vis[N]; int linked[N],p[N],q[N],n,anslink[N],ans; int ba,bl; void ins(int u,int v){ g[u][v]=g[v][u]=1; } bool match(int u){ vis[u]=1; for(int i=1;i<=n;i++){ int v=q[i];if(vis[v]||!g[u][v])continue; vis[v]=1; if(!linked[v]||match(linked[v])){ linked[v]=u; linked[u]=v; return true; } } return false; } void update(int tmp){ ans=tmp; for(int i=1;i<=ba;i++) anslink[i]=linked[i]; } void work(){ memset(linked,0,sizeof(linked)); int tmp=0; for(int i=1;i<=n;i++) if(!linked[p[i]]) for(int t=1;t<=10;t++){ memset(vis,0,sizeof(vis)); if(match(p[i])){ tmp++;break; }else{ for(int j=1;j<=n;j++){ int k=j+rand()%(n-j+1); swap(q[j],q[k]); } } } if(tmp>ans)update(tmp); } int b1(int i){ return ba+i; } int b2(int i){ return ba+bl+i; } int b3(int i){ return ba+bl+bl+i; } int calc(int x){ if(x<=ba+bl)return x-ba; if(x<=ba+bl+bl)return x-ba-bl; return x-ba-bl-bl; } int main(){ //freopen("a.in","r",stdin); int T;scanf("%d",&T); srand(541213); while(T--){ int e; scanf("%d%d%d",&ba,&bl,&e); memset(g,0,sizeof(g)); for(int i=1;i<=bl;i++) ins(b1(i),b2(i)); n=ba+bl*3; int a,b; while(e--){ scanf("%d%d",&a,&b); ins(a,b1(b)); ins(a,b2(b)); ins(a,b3(b)); } ans=0; for(int i=1;i<=n;i++)p[i]=q[i]=i; for(int k=1;k<=10;k++){ for(int i=1;i<=ba;i++){ int j=i+rand()%(ba-i+1); swap(p[i],p[j]); } for(int i=1;i<=n;i++){ int j=i+rand()%(n-i+1); swap(q[i],q[j]); } work(); } printf("%d\n",ans-ba); for(int i=1;i<=ba;i++) printf("%d ",calc(anslink[i])); putchar('\n'); } return 0; }