【CF878C】Tournament
题意:有k个项目,n个运动员,第i个运动员的第j个项目的能力值为aij。一场比赛可以通过如下方式进行:
每次选出2个人和一个项目,该项目能力值高者获胜,败者被淘汰,胜者继续比赛。最后一个人是冠军。
在一场比赛中,你可以任意安排比赛顺序,任意选择每次的参赛者和项目,现在想知道的是有多少人可能成为最后的冠军。
为了加大难度,一共有n次询问,第i次询问是前i个人进行比赛,问最终由多少人可能成为总冠军。
n<=50000,k<=10,aij<=10^9
题解:只要敢想就去写吧。
我们将所有人看成一张n个点的有向图,如果i的某项能力值比j高,则从i到j连一条有向边。我们将得到的整个图中的强连通分量缩成点,那么最终得到的一定是一条链。其中每个强联通分量中每一项的最小值都比下一个强联通分量的最大值还大。然后我们依次加入每个点,考虑如何维护这条链。
在加入第i个人时,对于每个项目,我们可以在set中找到这个人的前驱和后继,并记录二者在链中的位置。令a为每个项目中前驱位置的最大值,b为每个项目中后继位置的最小值,如果a>b,则说明i能打过a,b能打过i,并且a能打过b,出现了一个环!我们将这个环暴力缩掉即可;如果a=b,我们将i加入到a的强联通分量即可;如果a<b,那么a=b-1,我们把i加到ab中间即可。
可以用并查集维护连通性,链表维护整条链。
#include <cstdio> #include <cstring> #include <iostream> #include <set> #include <utility> #define mp(A,B) make_pair(A,B) using namespace std; const int maxn=50010; int n,m,last; int f[maxn],mx[maxn][11],mn[maxn][11],siz[maxn],nxt[maxn],v[11]; set<pair<int,int> > s[11]; set<pair<int,int> >::iterator it; inline int rd() { int ret=0,f=1; char gc=getchar(); while(gc<'0'||gc>'9') {if(gc=='-') f=-f; gc=getchar();} while(gc>='0'&&gc<='9') ret=ret*10+(gc^'0'),gc=getchar(); return ret*f; } int find(int x) { return (f[x]==x)?x:(f[x]=find(f[x])); } inline void merge(int a,int b) { for(int i=1;i<=m;i++) mx[b][i]=max(mx[b][i],mx[a][i]),mn[b][i]=min(mn[b][i],mn[a][i]); siz[b]+=siz[a],f[a]=b; } int main() { n=rd(),m=rd(); int i,j,a,b,t; for(j=1;j<=m;j++) s[j].insert(mp(mx[1][j]=mn[1][j]=rd(),1)); f[1]=siz[1]=last=1; puts("1"); for(i=2;i<=n;i++) { a=b=0; for(j=1;j<=m;j++) { v[j]=rd(); it=s[j].upper_bound(mp(v[j],i)); if(it!=s[j].end()) { t=find((*it).second); if(!b||mx[t][j]<mx[b][j]) b=t; } if(it!=s[j].begin()) { it--,t=find((*it).second); if(!a||mx[t][j]>mx[a][j]) a=t; } s[j].insert(mp(v[j],i)); } f[i]=i,siz[i]=1; for(j=1;j<=m;j++) mx[i][j]=mn[i][j]=v[j]; if(!a) nxt[i]=b; else if(!b) last=i,nxt[a]=i; else if(a==b) merge(i,a); else if(mx[a][1]<mx[b][1]) nxt[a]=i,nxt[i]=b; else { for(t=b;t!=a;t=nxt[t],merge(t,b)); merge(i,b); nxt[b]=nxt[a]; if(a==last) last=b; } printf("%d ",siz[last]); } return 0; }