[Usaco2018 Open]Milking Order

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<j成立,并且xj<yj(也就是说,这两个挤奶顺序到某个位置之前都是完全相同的,在这个位置上x比y要小)。请帮助Farmer John求出为奶牛挤奶的最佳顺序。

Input
第一行包含N和M。
接下来的M行,每行描述了一个观察结果。
第i+1行描述了观察结果i,第一个数是观察结果中的奶牛数量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

HINT
这里,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,第一种是字典序较小的


不难发现,如果我们对一些可以满足的观察结果建有向图的话,会得到一个DAG,得到DAG之后我们就可以用拓扑+堆来选出字典序最小的点。因此本题问题就在于如果找出那个DAG

我们知道观察结果是要前缀满足的,因此我们可以用\(O(nm)\)的时间找出那个断点,不过这样不一定能过。所以我们改一下方法,二分枚举断点,将1~mid的观察结果全部加进去,然后判环,这样就可以在\(O(m\log n)\)的时间内求出DAG了

/*program from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read(){
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar())  if (ch=='-')    f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar())    x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}
inline void print(int x){
    if (x>=10)  print(x/10);
    putchar(x%10+'0');
}
const int N=1e5,M=5e4;
int pre[(N<<1)+10],now[N+10],child[(N<<1)+10],d[N+10];
int dfn[N+10],low[N+10],stack[N+10],num[N+10],milk[N+10];
bool instack[N+10];
int tot,Time,top,cnt,n,m;
vector<int>vec[M+10];
struct S1{
    #define ls (p<<1)
    #define rs (p<<1|1)
    #define fa (p>>1)
    int Q[N+10],len;
    void insert(int x){
        Q[++len]=x;
        int p=len;
        while (p!=1&&Q[p]<Q[fa])    swap(Q[p],Q[fa]),p=fa;
    }
    int Get(){
        int Ans=Q[1],p=1,son; Q[1]=Q[len--];
        while (ls<=len){
            if (rs>len||Q[ls]<Q[rs])    son=ls;
            else    son=rs;
            if (Q[p]>Q[son])    swap(Q[p],Q[son]),p=son;
            else    break;
        }
        return Ans;
    }
}Heap;
void join(int x,int y){pre[++tot]=now[x],now[x]=tot,child[tot]=y,d[y]++;}
void tarjan(int x){
    dfn[x]=low[x]=++Time;
    instack[stack[++top]=x]=1;
    for (int p=now[x],son=child[p];p;p=pre[p],son=child[p]){
        if (!dfn[son])  tarjan(son),low[x]=min(low[x],low[son]);
        else    if (instack[son])   low[x]=min(low[x],dfn[son]);
    }
    if (dfn[x]==low[x]){
        instack[x]=0,num[x]=++cnt;
        while (stack[top]!=x)   instack[stack[top]]=0,num[stack[top--]]=cnt;
        top--;
    }
}
void init(){
    tot=Time=cnt=0;
    memset(d,0,sizeof(d));
    memset(now,0,sizeof(now));
    memset(dfn,0,sizeof(dfn));
}
bool check(int x){
    init();
    for (int i=1;i<=x;i++)  for (int j=1;j<(int)vec[i].size();j++)  join(vec[i][j-1],vec[i][j]);
    for (int i=1;i<=n;i++)  if (!dfn[i])    tarjan(i);
    return cnt==n;
}
void topo(){
    int T=0;
    for (int i=1;i<=n;i++)  if (!d[i])  Heap.insert(i);
    while (Heap.len){
        int x=(milk[++T]=Heap.Get());
        for (int p=now[x],son=child[p];p;p=pre[p],son=child[p])
            if (!--d[son])  Heap.insert(son);
    }
}
int main(){
    n=read(),m=read();
    for (int i=1;i<=m;i++)  for (int j=read();j;j--)    vec[i].push_back(read());
    int l=1,r=m,res=0;
    while (l<=r){
        int mid=(l+r)>>1;
        if (check(mid)) res=mid,l=mid+1;
        else    r=mid-1;
    }
    init();
    for (int i=1;i<=res;i++)    for (int j=1;j<(int)vec[i].size();j++)  join(vec[i][j-1],vec[i][j]);
    topo();
    for (int i=1;i<=n;i++)  printf("%d",milk[i]),i==n?putchar('\n'):putchar(' ');
    return 0;
}

转载于:https://www.cnblogs.com/Wolfycz/p/9626801.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值