图论
最大流
dinic
bool bfs(){ for(int i=sb;i<=se;i++) dep[i]=0; dep[sb]=1; queue<int> q; q.push(sb); int u,v; while(!q.empty()){ u=q.front(); q.pop(); for(int i=head[u];~i;i=S[i].ne){ v=S[i].v; if(S[i].w>0&&!dep[v]){ dep[v]=dep[u]+1; if(v==se) return true; q.push(v); } } } return false; } ll dfs(int u,ll minf){ if(u==se||!minf) return minf; ll flow; int v; for(int &i=cur[u];~i;i=S[i].ne){ v=S[i].v; if(S[i].w>0&&dep[v]==dep[u]+1){ flow=dfs(v,min(S[i].w,minf)); if(flow){ S[i].w-=flow; S[i^1].w+=flow; return flow; } } } return 0; } ll dinic(){ ll maxf=0,flow; while(bfs()){ for(int i=sb;i<=se;i++) cur[i]=head[i]; while(flow=dfs(sb,inf)) maxf+=flow; } return maxf; }
isap
bool bfs(){ for(int i=sb;i<=se;i++) dep[i]=0; dep[se]=1; queue<int> q; q.push(se); int u,v; while(!q.empty()){ u=q.front(); q.pop(); for(int i=head[u];~i;i=S[i].ne){ v=S[i].v; if(!dep[v]){ dep[v]=dep[u]+1; q.push(v); } } } return dep[sb]!=0; } ll aug(){ ll minf=inf; for(int i=lu[se];~i;i=lu[S[i^1].v]) minf=min(minf,S[i].w); for(int i=lu[se];~i;i=lu[S[i^1].v]){ S[i].w-=minf; S[i^1].w+=minf; } return minf; } ll ispa(){ ll maxf=0; bfs(); for(int i=sb;i<=se;i++){ lu[i]=-1; num[dep[i]]++; cur[i]=head[i]; } int u=sb,v; while(dep[sb]<n){ if(u==se){ maxf+=aug(); u=sb; } bool flag=false; for(int &i=cur[u];~i;i=S[i].ne){ v=S[i].v; if(S[i].w>0&&dep[u]==dep[v]+1){ flag=true; lu[v]=i; u=v; break; } } if(!flag){ int mind=n-1; for(int i=head[u];~i;i=S[i].ne){ v=S[i].v; if(S[i].w>0) mind=min(mind,dep[v]); } if(--num[dep[u]]==0) break; num[dep[u]=mind+1]++; cur[u]=head[u]; if(u!=sb) u=S[lu[u]^1].v; } } return maxf; }
最小费用最大流
bool spfa(int n) { queue<int> q; for(int i=0;i<=n;i++) { dis[i]=inf; vis[i]=0; flow[i]=inf; lu[i]=-1; } dis[s]=0; vis[s]=1; q.push(s); while(!q.empty()) { int u=q.front(); q.pop(); vis[u]=0; for(int i=head[u];i!=-1;i=S[i].ne) { int v=S[i].v; if(S[i].w>0&&dis[v]>dis[u]+S[i].val) { lu[v]=i; dis[v]=dis[u]+S[i].val; flow[v]=min(flow[u],S[i].w); if(!vis[v]) { vis[v]=1; q.push(v); } } } } return dis[t]!=inf; } void mfml(int n) { int ans=0,ansc=0; while(spfa(n)) { ans+=flow[t]; ansc+=flow[t]*dis[t]; for(int i=lu[t];i!=-1;i=lu[S[i^1].v]) { S[i].w-=flow[t]; S[i^1].w+=flow[t]; } } printf("%d %d\n",ans,ansc); }
zwk费用流
bool spfa(int n){ for(int i=0;i<=n;i++){ dis[i]=inf; vis[i]=false; } dis[se]=0; vis[se]=1; deque<int> q; q.push_back(se); while(!q.empty()){ int u=q.front(); q.pop_front(); vis[u]=false; for(int i=head[u],v;~i;i=S[i].ne){ v=S[i].v; if(S[i^1].w>0&&dis[v]>dis[u]-S[i].val){ dis[v]=dis[u]-S[i].val; if(!vis[v]){ vis[v]=true; if(!q.empty()&&dis[v]<dis[q.front()]) q.push_front(v); else q.push_back(v); } } } } return dis[sb]!=inf; } int dfs(int u,int minf){ if(u==se){ vis[se]=true; return minf; } vis[u]=true; int flow=0,temp,v; for(int i=head[u];~i;i=S[i].ne){ v=S[i].v; if(!vis[v]&&S[i].w>0&&dis[v]==dis[u]-S[i].val){ temp=dfs(v,min(S[i].w,minf-flow)); if(temp){ S[i].w-=temp; S[i^1].w+=temp; flow+=temp; ansc+=S[i].val*temp; } if(flow==minf) break; } } return flow; } void mfml(int n){ int ans=0; ansc=0; while(spfa(n)){ vis[se]=true; while(vis[se]){ for(int i=0;i<=n;i++) vis[i]=false; ans+=dfs(sb,inf); } } printf("%d %d\n",ans,ansc); }
欧拉回路
#includeconst int N=101108,M=201108; struct Side{ int v,ne,ok,id; }S[M<<1]; int t,n,m,sn,cnt,head[N],cur[N],in[N],out[N],fa[N],ans[M<<1]; void init(){ sn=0; for(int i=1;i<=n;i++){ fa[i]=i; head[i]=-1; in[i]=out[i]=0; } } void add(int u,int v,int id){ S[sn].ok=1; S[sn].id=id; S[sn].v=v; S[sn].ne=head[u]; head[u]=sn++; } int find(int x){ return fa[x]==x ? x : fa[x]=find(fa[x]); } void bing(int x,int y){ int fx=find(x),fy=find(y); if(fx!=fy) fa[fx]=fy; return ; } void dfs(int u){ for(int &i=cur[u];~i;i=S[i].ne){ if(S[i].ok){ S[i].ok=0; int temp=i; if(t==1) S[i^1].ok=0; dfs(S[i].v); ans[cnt++]=S[temp].id; if(i==-1) break; } } } int main(){ int u,v; while(~scanf("%d",&t)){ scanf("%d%d",&n,&m); init(); for(int i=1;i<=m;i++){ scanf("%d%d",&u,&v); bing(u,v); if(t==1){ in[u]++; in[v]++; add(u,v,i); add(v,u,-i); }else{ in[v]++; out[u]++; add(u,v,i); } } int beg=1,flag=1,num=0; for(int i=1;i<=n;i++){ cur[i]=head[i]; if(fa[i]==i&&in[i]) num++; if(t==1){ if(in[i]&1) flag=0; else if(in[i]) beg=i; }else{ if(in[i]!=out[i]) flag=0; else if(in[i]) beg=i; } if(!flag||num>1) break; } if(flag&&num<=1){ printf("YES\n"); if(num){ cnt=0; dfs(beg); for(int i=cnt-1;i>=0;i--) printf("%d%c",ans[i]," \n"[i==0]); } }else printf("NO\n"); } return 0; }
混合图欧拉回路
然后对于每个出度大于入度的点,我们把它与源点连一条流量为(出度-入度)/2的边,(出度-入度)/2其实就是需要改变多少条连到它上面的无向边,使得它出度等于入度。而对于入度大于出度的点,则是与汇点连一条流量为(入度-出度)/2的边。
所以最终能不能有欧拉回路,也就是看能不能满流,让每个点都满足出度等于入度的要求。
而欧拉路径的话,就是最多只能有两个出度与入度奇偶性不同的点,也就是终点和起点,然后我们找到这两个点,给它们连一条无向边,那处理就跟欧拉回路的一样了。
至于路径的输出,我们就看之前流量为1的那些边,如果流量变为0了,说明反向了,否则就是我们原定的方向,就用这些新的有向边和原来的有向边,然后有向图的欧拉回路就ok了
强连通分量
void tarjan(int u){ ins[u]=true; sta[++tn]=u; dfn[u]=low[u]=++dn; for(int i=head[u],v;~i;i=S[i].ne){ v=S[i].v; if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]); }else if(ins[v]) low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]){ col[u]=++cn; ins[u]=false; size[cn]=1; minc[cn]=u; while(sta[tn]!=u){ col[sta[tn]]=cn; size[cn]++; minc[cn]=min(minc[cn],sta[tn]); ins[sta[tn--]]=false; } if(size[cn]>size[ansc]||(size[cn]==size[ansc]&&minc[cn]cn; tn--; } }
割点
对于一个割点来说,它割掉之后产生的连通块就是能让它是割点的点个数,因为那些点就代表着它那个连通块。
void findc(int u,int fa){ dfn[u]=low[u]=++dn; int v,vvs=vv[u].size(),son=0; for(int i=0;i){ v=vv[u][i]; if(!dfn[v]){ if(u==fa) son++; findc(v,u); low[u]=min(low[u],low[v]); if(low[v]>=dfn[u]&&u!=fa) isc[u]=true; }else low[u]=min(low[u],dfn[v]); } if(u==fa&&son>=2) isc[u]=true; if(isc[u]) ans++; }
割边
void findq(int u){ dfn[u]=low[u]=++dn; int v,id; for(int i=head[u];~i;i=S[i].ne){ v=S[i].v; id=S[i].id; if(!dfn[v]){ fp[v]=id; findq(v); low[u]=min(low[u],low[v]); }else if(id!=fp[u]) low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]&&fp[u]!=-1){ if(ans==-1) ans=val[fp[u]]; else ans=min(ans,val[fp[u]]); } }
点双连通分量
void pdc(int u,int fa){ dfn[u]=low[u]=++dn; int v,son=0; for(int i=head[u];~i;i=S[i].ne){ v=S[i].v; if(!dfn[v]){ if(u==fa) son++; sta.push(Side(u,v)); pdc(v,u); low[u]=min(low[u],low[v]); if(low[v]>=dfn[u]){ if(u!=fa) isc[u]=true; bf[++bn]=u; while(!sta.empty()){ int su=sta.top().u,sv=sta.top().v; if(su==u&&sv==v) break; sta.pop(); if(bin[su]!=bn) bin[su]=bn; if(bin[sv]!=bn) bin[sv]=bn; } sta.pop(); } }else if(v!=fa){ low[u]=min(low[u],dfn[v]); if(dfn[u]>dfn[v]) sta.push(Side(u,v)); } } if(u==fa&&son>=2) isc[u]=true; }
边双连通分量
void dfs(int u){ bin[u]=bn; vis[u]=true; for(int i=head[u];~i;i=S[i].ne){ if(isq[S[i].id]) continue; if(!vis[S[i].v]) dfs(S[i].v); } }
个有桥的连通图,如何把它通过加边变成边双连通图:统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
一般图最大匹配
#include#include using namespace std; const int N=511,M=2e5+11; struct Side{ int v,ne; }S[M<<1]; queue<int> q; int n,sn,fn,head[N],pp[N],col[N],pre[N],fa[N],vis[N]; //pp[i] i节点匹配的节点,pp[i]-i就是匹配边 //col[i] i节点在当前交错树的染色,用于判断奇环 //pre[i] 记录交错树中i的前一个节点 i-pre[i]就是非匹配边 //fa[i] i节点当前交错树花的lca,用于缩点,和判断奇环 //vis用来判断在找lca时,某个点是否走过了 void init(){ sn=fn=0; for(int i=0;i<=n;i++){ pp[i]=0; vis[i]=0; head[i]=-1; } } void add(int u,int v){ S[sn].v=v; S[sn].ne=head[u]; head[u]=sn++; } int find(int x){ return fa[x]==x ? x : fa[x]=find(fa[x]); } int flca(int x,int y){ ++fn; x=find(x),y=find(y); //当发现奇环时,这两个节点在交错树的深度是相同的。 //两个节点匹配边-非匹配边交替往前找,遇到已经走到过的点,那就是花的lca了 while(vis[x]!=fn){ vis[x]=fn; x=find(pre[pp[x]]); if(y){ int temp=x;x=y;y=temp; } } return x; } void blossom(int x,int y,int fl){ //因为开花时奇环可以双向走,因此pre边也要变成双向的。 while(find(x)!=fl){ pre[x]=y;//x是原本的黑点,y是原本的白点,原先已经有pre[y]=x y=pp[x]; if(col[y]==2){//原来是白的,变成黑的了,继续增广 col[y]=1; q.push(y); } //因为有些点可能已经缩在某朵花里了,所以只对还映射自己的点缩点 if(find(x)==x) fa[x]=fl; if(find(y)==y) fa[y]=fl; x=pre[y];//和上面的y=pp[x]就实现匹配边-非匹配边交替走 } } int aug(int u){ for(int i=0;i<=n;i++){ fa[i]=i; pre[i]=0; col[i]=0; }//fa pre col都是相对当前的交错树而言,所以每次都要清空 while(!q.empty()) q.pop(); q.push(u); col[u]=1; while(!q.empty()){ u=q.front(); q.pop(); for(int i=head[u],v;~i;i=S[i].ne){ v=S[i].v; if(find(u)==find(v)||col[v]==2) continue; if(!col[v]){ pre[v]=u;col[v]=2; if(!pp[v]){ for(int x=v,y;x;x=y){ y=pp[pre[x]]; pp[x]=pre[x]; pp[pre[x]]=x; }//非匹配边-匹配边,返回去修改 return 1; } col[pp[v]]=1;q.push(pp[v]); }else{//发现奇环,进行开花(缩点) int fl=flca(u,v); blossom(u,v,fl); blossom(v,u,fl); } } } return 0; } int main(){ int m,u,v; while(~scanf("%d%d",&n,&m)){ init(); while(m--){ scanf("%d%d",&u,&v); add(u,v); add(v,u); } int ans=0; for(int i=1;i<=n;i++) if(!pp[i]) ans+=aug(i); printf("%d\n",ans); for(int i=1;i<=n;i++) printf("%d%c",pp[i]," \n"[i==n]); } return 0; }
一般图最大团
const int N=111; bool mp[N][N]; int n,ans,mcq[N],vis[N],ids[N]; bool dfs(int *adj,int tot,int num){ if(tot==0){ if(num>ans){ ans=num; for(int i=0;ivis[i]; return true; } return false; } int temp[N],cnt; for(int i=0;i ){ if(num+tot-i<=ans) return false; if(num+mcq[adj[i]]<=ans) return false; cnt=0; for(int j=i+1;j if(mp[adj[i]][adj[j]]) temp[cnt++]=adj[j]; vis[num]=adj[i]; if(dfs(temp,cnt,num+1)) return true; } return false; } int maxcq(){ int adj[N],cnt; ans=0; for(int i=n;i>=1;i--){ cnt=0; vis[0]=i; for(int j=i+1;j<=n;j++) if(mp[i][j]) adj[cnt++]=j; dfs(adj,cnt,1); mcq[i]=ans; } return ans; }
HK优化匈牙利
bool searchp() { queue<int>q; dis=INF; CLR(dx,-1);//memset CLR(dy,-1); for(int i=1;i<=n;i++) { if(mx[i]==-1) { q.push(i); dx[i]=0; } } while(!q.empty()) { int u=q.front(); q.pop(); if(dx[u]>dis) break; for(int i=head[u];~i;i=e[i].next) { int v=e[i].v; if(dy[v]==-1) { dy[v]=dx[u]+1; if(my[v]==-1) dis=dy[v]; else { dx[my[v]]=dy[v]+1; q.push(my[v]); } } } } return dis!=INF; } bool dfs(int u) { for(int i=head[u];~i;i=e[i].next) { int v=e[i].v; if(vis[v]||(dy[v]!=dx[u]+1)) continue; vis[v]=1; if(my[v]!=-1&&dy[v]==dis) continue; if(my[v]==-1||dfs(my[v])) { my[v]=u; mx[u]=v; return true; } } return false; } int maxMatch() { int res = 0; CLR(mx,-1); CLR(my,-1); while(searchp()) { CLR(vis,0); for(int i=1;i<=n; i++) if(mx[i] == -1 && dfs(i)) res++; } return res; }
二分图最大权匹配KM
//bfsn3 #include#include #include using namespace std; typedef long long ll; const int N=411; const ll inf=1e18+7; queue<int> q; ll ex[N],ey[N],del[N],val[N][N]; int nx,ny,vn,px[N],py[N],pre[N],visx[N],visy[N]; void init(){ vn=0; for(int i=0;i<=ny;i++) py[i]=visy[i]=0; for(int i=0;i<=nx;i++) px[i]=visx[i]=0; for(int i=0;i<=ny;i++) for(int j=0;j<=nx;j++) val[i][j]=0; } bool aug(){ int u,v; ll temp; while(!q.empty()){ u=q.front(); q.pop(); for(int i=1;i<=nx;i++){ if(visx[i]==vn) continue; temp=ey[u]+ex[i]-val[u][i]; if(!temp){ pre[i]=u; if(!px[i]){ for(int x=u,y=i,z;y;x=pre[y]){ z=py[x]; py[x]=y; px[y]=x; y=z; }//跟带花树的类似,一样是匹配边非匹配边交替修改 return 1; } visx[i]=vn; if(visy[px[i]]!=vn){ visy[px[i]]=vn; q.push(px[i]); } }else if(temp<del[i]){ pre[i]=u; del[i]=temp; } } } return false; } void km(){ for(int i=1;i<=ny;i++){ ey[i]=0; for(int j=1;j<=nx;j++) ey[i]=max(ey[i],val[i][j]); } for(int i=1;i<=nx;i++) ex[i]=0; for(int i=1;i<=ny;i++){ for(int j=1;j<=nx;j++){ del[j]=inf; pre[j]=0; } while(!q.empty()) q.pop(); q.push(i); visy[i]=++vn; while(true){ if(aug()) break; ll d=inf; for(int j=1;j<=nx;j++) if(visx[j]!=vn) d=min(d,del[j]); for(int j=1;j<=ny;j++) if(visy[j]==vn) ey[j]-=d; for(int j=1;j<=nx;j++) if(visx[j]==vn) ex[j]+=d; else if(del[j] d; //上面的跟dfs的处理一样 bool flag=false; for(int j=1;j<=nx;j++){//在调整了期望值后 //找到能参与进匹配的男生 if(visx[j]==vn||del[j]!=0) continue; if(!px[j]){ for(int x=pre[j],y=j,z;y;x=pre[y]){ z=py[x]; py[x]=y; px[y]=x; y=z; } flag=true; break; } visx[j]=vn; if(visy[px[j]]!=vn){ visy[px[j]]=vn; q.push(px[j]); } } if(flag) break; } } } void solve(int n){ km(); ll ans=0; for(int i=1;i<=n;i++) ans+=val[px[i]][i]; printf("%lld\n",ans); for(int i=1;i<=n;i++){ if(!val[px[i]][i]) px[i]=0; printf("%d%c",px[i]," \n"[i==n]); } } int main(){ ll w; int m,u,v,n; while(~scanf("%d%d%d",&nx,&ny,&m)){ n=nx; if(nx ny; init(); while(m--){ scanf("%d%d%lld",&v,&u,&w); val[u][v]=w; } solve(n); } return 0; }
最小点覆盖:二分图中,选取最少的点数,使这些点和所有的边都有关联(把所有的边的覆盖),叫做最小点覆盖。
解决方法:最小点覆盖数 = 最大匹配数
最小路径覆盖:这跟一般图的是同一个。给定有向图G=(V,E)。设P 是G 的一个简单路(顶点不相交)的集合。如果V 中每个顶点恰好在P 的一条路上,则称P是G 的一个路径覆盖。P 中路径可以从V 的任何一个顶点开始,长度也是任意的,特别地,可以为0。G 的最小路径覆盖是G 的所含路径条数最少的路径覆盖。
解决方法:对于G中每一个节点x,建立节点x1,x2。若x- >y存在边,则x1与y2之间连一条无向边,求这个二分图的最大匹配数即可。然后,最小路径覆盖=|G|-最大匹配数
最大独立集:独立集是指图G中两两互不相邻的顶点构成的集合。最大,就是点数最多。
解决方法:最大独立集=总数-最小覆盖集
三元环
#include#include #include using namespace std; typedef long long ll; const int N=100118; struct Side{ int v,ne; }S[2*N]; int sn,head[N],link[N],line[N],du[N],val[2*N],u[2*N],v[2*N]; inline void init(int n) { sn=0; for(int i=0;i<=n;i++) { head[i]=-1; du[i]=0; link[i]=0; line[i]=-1; } } inline void add(int u,int v) { S[sn].v=v; S[sn].ne=head[u]; head[u]=sn++; } int main() { int n,m; while(~scanf("%d%d",&n,&m)) { init(n); for(int i=0;i ) { val[i]=0; scanf("%d%d",&u[i],&v[i]); du[u[i]]++,du[v[i]]++; } for(int i=0;i ) { if(du[u[i]]<du[v[i]]) add(u[i],v[i]); else if(du[u[i]]>du[v[i]]) add(v[i],u[i]); else { if(u[i]<v[i]) add(u[i],v[i]); else add(v[i],u[i]); } } for(int i=0;i ) { int x=u[i],y=v[i]; for(int j=head[x];j!=-1;j=S[j].ne) { link[S[j].v]=x; line[S[j].v]=j; } for(int j=head[y];j!=-1;j=S[j].ne) { int z=S[j].v; if(link[z]==x) { val[i]++; val[j]++; val[line[z]]++; } } } ll ans=0; for(int i=0;i ) ans+=1ll*val[i]*(val[i]-1)/2; printf("%lld\n",ans); } return 0; }
CDQ分治陌上花开
#include#include #define lowb(x) (x&(-x)) using namespace std; const int N=100118,K=200118; struct Flower{ int id,s,c,m,w; friend bool operator == (const Flower &f1,const Flower &f2){ return f1.s==f2.s&&f1.m==f2.m&&f1.c==f2.c; } }F[N],temp[N]; int fn,ans[N]={0},rank[N]={0},summ[K]={0}; //ans[i]保存编号为i的花的等级,也就是属性小于或等于它的数目 bool cmp(const Flower &f1,const Flower &f2){ return f1.s==f2.s ? (f1.c==f2.c ? f1.m f2.s; } void updata(int m,int val) { while(m<=200000) { summ[m]+=val; m+=lowb(m); } } int getsum(int m) { int ans=0; while(m) { ans+=summ[m]; m-=lowb(m); } return ans; } void clear(int m) { while(m<=200000) { if(summ[m]) summ[m]=0; else break; m+=lowb(m); } } void cdq(int l,int r) { if(l==r) { ans[F[l].id]+=F[l].w-1;//因为相同属性的归到一类了 //所以还得加上F[l].w-1,也就是除它之外, //其他相同属性的花的数目 return ; } int mid=(l+r)>>1; cdq(l,mid); cdq(mid+1,r); int i=l,j=mid+1,k=l; while(i<=mid&&j<=r) { if(F[i].c<=F[j].c) { updata(F[i].m,F[i].w); temp[k++]=F[i++]; } else { ans[F[j].id]+=getsum(F[j].m); temp[k++]=F[j++]; } } while(i<=mid) temp[k++]=F[i++]; while(j<=r) { ans[F[j].id]+=getsum(F[j].m); temp[k++]=F[j++]; } for(i=l;i<=r;i++) { clear(F[i].m); F[i]=temp[i]; } } int main() { int n,k; while(~scanf("%d%d",&n,&k)) { for(int i=0;i ) { F[i].w=1; scanf("%d%d%d",&F[i].s,&F[i].c,&F[i].m); } sort(F,F+n,cmp); fn=0; //去重 for(int i=0;i ) { if(i&&F[i]==F[i-1])//如果和前一朵花属性相同,归到一类 F[fn-1].w++; else { F[fn]=F[i]; F[fn].id=fn++; } } //原先n朵花去重就只有fn朵 cdq(0,fn-1); for(int i=0;i ) { rank[ans[F[i].id]]+=F[i].w; ans[F[i].id]=0; } for(int i=0;i ) { printf("%d\n",rank[i]); rank[i]=0; } } return 0; }
biset5维偏序
#include#include using namespace std; const int N=30118; int a[N][6],b[6][N]; //a[i][j]第i个人第j门功课排第几, b[i][j]第i功课排第j的是谁 bitset c[6][N],ans;//c[i][j]第i门功课排第j的人,名次比他高的有谁 int main() { int n; while(~scanf("%d",&n)) { for(int i=0;i ) for(int j=0;j<5;j++) { scanf("%d",&a[i][j]); a[i][j]--;//因为我是从0开始的,所以这里-- b[j][a[i][j]]=i; c[j][i].reset(); } for(int i=0;i<5;i++) for(int j=1;j ) { c[i][j]=c[i][j-1]; c[i][j].set(b[i][j-1]); } for(int i=0;i ) { ans=c[0][a[i][0]]; for(int j=1;j<5;j++) ans&=c[j][a[i][j]]; printf("%d\n",ans.count()); } } return 0; }