Description
Farmer John的N头奶牛(1≤N≤10^5),仍然编号为1…N,正好闲得发慌。因此,她们发展了一个与Farmer John每天早上为她们挤牛奶的时候的排队顺序相关的复杂的社会阶层。
经过若干周的研究,Farmer John对他的奶牛的社会结构总计进行了M次观察(1≤M≤50,000)。每个观察结果都是他的某些奶牛的一个有序序列,表示这些奶牛应该以与她们在序列中出现的顺序相同的顺序进行挤奶。比方说,如果Farmer John的一次观察结果是序列2、5、1,Farmer John应该在给奶牛5挤奶之前的某个时刻给奶牛2挤奶,在给奶牛1挤奶之前的某个时刻给奶牛5挤奶。
Farmer John的观察结果是按优先级排列的,所以他的目标是最大化X的值,使得他的挤奶顺序能够符合前X个观察结果描述的状态。当多种挤奶顺序都能符合前X个状态时,Farmer John相信一个长期以来的传统——编号较小的奶牛的地位高于编号较大的奶牛,所以他会最先给编号最小的奶牛挤奶。更加正式地说,如果有多个挤奶顺序符合这些状态,Farmer John会采用字典序最小的那一个。挤奶顺序x的字典序比挤奶顺序y要小,如果对于某个j,xi=yi对所有i 请帮助Farmer John求出为奶牛挤奶的最佳顺序。
Input
第一行包含N和M。
接下来的M行,每行描述了一个观察结果。第i+1行描述了观察结果ii,第一个数是观察结果中的奶牛数量mi,后面是一列mi个整数,给出这次观察中奶牛的顺序。所有mi的和至多为200,000。
Output
输出N个空格分隔的整数,给出一个1…N的排列,为Farmer John给他的奶牛们挤奶应该采用的的顺序。
Sample Input
4 3
3 1 2 3
2 4 2
3 3 4 1
Sample Output
1 4 2 3
Sample Description
这里,Farmer John有四头奶牛,他的挤奶顺序应该是奶牛1在奶牛2之前、奶牛2在奶牛3之前(第一个观察结果),奶牛4在奶牛2之前(第二个观察结果),奶牛3在奶牛4之前、奶牛4在奶牛1之前(第三个观察结果)。前两个观察结果可以同时被满足,但是Farmer John不能同时满足所有的规则,因为这样的话会要求奶牛1在奶牛3之前,同时奶牛3在奶牛1之前。
这意味着总共有两种可能的挤奶顺序:1 4 2 3和4 1 2 3,第一种是字典序较小的。
思路
- 文末详细
无营养考试心路历程,代码党可忽略 - 开两个数组,一个存储前k+1个观察,类似探头作用,探测到不合法就跳出(前k个合法,第k+1个出现环),另一个存储合法拓扑图(前k个观察)
- 因为字典序最小,想到用小根堆(优先队列)维护
- 似乎有题解说:考虑二分,首先先把条件记录下来 方便以后建图用,然后以[1,M]为区间二分 mid把前 mid 个条件建出图来
- 但其实不需要,此代码洛谷亲测536ms 无O2
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
int n,m,in[100005],temp[50005],tin[100005],vis[100005],flag;
vector<int> g[100005],tg[100005];
priority_queue<int,vector<int>,greater<int> > q;
void dfs(int x)
{
vis[x]=-1;
if(flag) return;
for(int i=0;i<tg[x].size();++i)
{
if(vis[tg[x][i]]==-1){flag=1; return;}
else if(!vis[tg[x][i]]) dfs(tg[x][i]);
}
vis[x]=1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
{
int k; scanf("%d%d",&k,&temp[1]);
for(int j=2;j<=k;++j)//探头作用,试着加入此观察
{
scanf("%d",&temp[j]); ++tin[temp[j]];
tg[temp[j-1]].push_back(temp[j]);
}
memset(vis,0,sizeof(vis));
for(int j=1;j<=k;++j)//判断是否有环
{
if(flag) break;
if(!vis[temp[j]]) dfs(temp[j]);
}
if(flag) break;
for(int j=2;j<=k;++j) ++in[temp[j]],g[temp[j-1]].push_back(temp[j]);//加入真实拓扑图
}
for(int i=1;i<=n;++i) {if(!in[i]) q.push(i);}
while(!q.empty())
{
int u=q.top(); q.pop();
printf("%d ",u);
for(int i=0;i<g[u].size();++i)
if(!(--in[g[u][i]])) q.push(g[u][i]);
}
puts("");
return 0;
}
心路历程
- 最开始想用并查集判断合法性,自测时一组数据直接WA
//1.0初始代码片段
for(int i=1;i<=n;++i) fa[i]=i;
for(int i=1;i<=m;++i)
{
int k,flag=0,u,v; scanf("%d%d",&k,&u)
for(int j=2;j<=k;++j)
{
scanf("%d",&v);
int f1=getfa(v),f2=getfa(u);
if(f1!=f2)
{
fa[f1]=f2;
++in[v]; g[u].push_back(v);
}
else {flag=1; break;}
u=v;
}
if(flag) break;
}
for(int i=1;i<=n;++i) {if(!in[i]) q.push(i);}
while(!q.empty())
{
int u=q.top(); q.pop();
printf("%d ",u);
for(int i=0;i<g[u].size();++i)
if(!(--in[g[u][i]])) q.push(g[u][i]);
}
- 之后注意到是以整次观察为单位,不拆边,想到用另一个数组暂存一下
for(int i=1;i<=m;++i)
{
int k,flag=0; scanf("%d%d",&k,&temp[1]);
for(int j=2;j<=k;++j)
{
scanf("%d",&temp[j]);
int f1=getfa(temp[j]),f2=getfa(temp[j-1]);
if(f1!=f2) fa[f1]=f2;
else {flag=1; break;}
}
if(flag) break;
for(int j=2;j<=k;++j)
{
int f1=getfa(temp[j]),f2=getfa(temp[j-1]);
fa[f1]=f2;
++in[temp[j]]; g[temp[j-1]].push_back(temp[j]);
}
}
- 头晕尝试用ftemp存储两次getfa的值,打完才想起已经路径压缩没必要。。。
<代码略> - 发现是有向图,放弃使用并查集,有向图判环,终极代码
<AC代码如上> - 想着会每次memset会Time Out,然而没有。。。