QOJ.ac

QOJ

Type: Editorial

Status: Open

Posted by: Coffee_zzz

Posted at:

Last updated:

Back to Problem

New Editorial for Problem #22859

#22859. Longest Path on DAG

题意

这是一道交互题。

给定 $N$ 个编号为 $1\sim N$ 的点,每个点为黑色或白色。存在一张隐藏的有向二分图 $G$,其中每条边 $(u,v)$ 都满足 $u\lt v$,且 $u,v$ 颜色不同。

你可以进行至多 $15000$ 次交互操作,共有两种:

  • in u V:选择一个黑点 $u$ 和一个白点集合 $V$,询问是否存在 $v\in V$,使得边 $(v,u)$ 存在。
  • out u V:选择一个黑点 $u$ 和一个白点集合 $V$,询问是否存在 $v\in V$,使得边 $(u,v)$ 存在。

你需要通过不超过 $15000$ 次询问,找出并输出图 $G$ 中任意一条最长路径。

$2$ 秒,$1\le N\le 500$。

题解

设黑点集合为 $B$,白点集合为 $W$。

考虑在知道整个图的情况下应该如何求最长路径:对于每个结点 $u$,记录以 $u$ 为终点的最长路径的长度 $f_u$ 和前驱 $pre_u$,那么有 $f_u=\max_{(v,u) \in E} f_v+1$。

对于黑点 $u$,可以直接将 $[1,u)$ 中的白点 $v$ 按照 $f_v$ 排序,通过 $\mathcal O(\log N)$ 次询问,二分求出 $f_v$ 最大的满足 $(v,u) \in E$ 的白点 $v$。

对于白点 $v$,同样需要计算 $[1,v)$ 中所有黑点 $u$ 的 $f_u$ 的最大值,但由于询问格式限制,其至少需要 $\mathcal O(N)$ 次询问来计算。

如果我们对每个白点都这样询问,总询问次数会达到 $\mathcal O(N^2)$,因此我们可以尝试将若干询问合并起来。

对于黑点集合 $B'$ 和白点集合 $W'$ 满足 $\max\{B'\}< \min\{W'\}$,我们考虑如何快速计算 $B'$ 对 $W'$ 的贡献:不断取出 $B'$ 中 $f_u$ 最大的黑点 $u$,并递归找到所有满足 $(u,v) \in E$ 的白点 $v$,用 $f_u+1$ 更新这些白点的 $f_v$,最后将这些白点从 $W'$ 中删除。设 $|W'|=m$,由于每个白点只会被计算一次,所以这样做的询问次数为 $\mathcal O(m \log m)$。

具体实现可以考虑 CDQ 分治:设当前分治区间为 $[l,r]$,我们可以按照如下方式进行计算:

  • 如果 $l=r$:
    • 如果 $l$ 为黑点,二分求出 $f_l$ 的值;
    • 如果 $l$ 为白点,$f_l$ 的值在此前已经计算好了。
  • 否则:
    • 递归 $[l,mid]$;
    • 对 $[l,mid]$ 中的每个黑点,计算其对 $[mid+1,r]$ 中每个白点的贡献并更新。
    • 递归 $[mid+1,r]$。

总询问次数为 $\mathcal O(N \log^2 N)$,可以通过。

代码

const int N=505;
int n,f[N],pre[N],q[N];
bool s[N],vis[N],ask[N];
pii p[N];
void getb(int u){
    int tot=0;
    for(int v=1;v<u;v++) if(!s[v]) p[++tot]={f[v],v};
    sort(p+1,p+tot+1);
    int l=0,r=tot;
    while(l<r){
        int m=(l+r+1)/2,res;
        cout<<"in "<<u<<' ';
        for(int i=1;i<=n;i++) vis[i]=0;
        for(int i=m;i<=r;i++) vis[p[i].se]=1;
        for(int i=1;i<=n;i++) cout<<vis[i];
        cout<<endl;
        cin>>res;
        if(res==0) r=m-1;
        else l=m;
    }
    f[u]=f[p[r].se]+1,pre[u]=p[r].se;
}
void search(int u,int l,int r){
    int res;
    cout<<"out "<<u<<' ';
    for(int i=1;i<=n;i++) vis[i]=0;
    for(int i=l;i<=r;i++) vis[q[i]]=1;
    for(int i=1;i<=n;i++) cout<<vis[i];
    cout<<endl;
    cin>>res;
    if(!res) return;
    if(l==r){
        if(f[u]+1>f[q[l]]) f[q[l]]=f[u]+1,pre[q[l]]=u;
        ask[q[l]]=0;
    }
    else{
        int m=(l+r)/2;
        search(u,l,m);
        search(u,m+1,r);
    }
}
void getw(int a,int b,int c,int d){
    int tot=0;
    for(int u=a;u<=b;u++) if(s[u]) p[++tot]={f[u],u};
    sort(p+1,p+tot+1);
    for(int i=1;i<=n;i++) ask[i]=0;
    for(int v=c;v<=d;v++) if(!s[v]) ask[v]=1;
    for(int i=tot;i>=1;i--){
        int cnt=0;
        for(int v=c;v<=d;v++) if(ask[v]) q[++cnt]=v;
        search(p[i].se,1,cnt);
    }
}
void dfs(int l,int r){
    if(l==r){
        if(!s[l]) return;
        getb(l);
    }
    else{
        int m=(l+r)>>1;
        dfs(l,m);
        getw(l,m,m+1,r);
        dfs(m+1,r);
    }
}
void print(int u){
    if(!u) return;
    print(pre[u]);
    cout<<u<<' ';
}
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>s[i],f[i]=1,pre[i]=0;
    dfs(1,n);
    int ma=0,u=0;
    for(int i=1;i<=n;i++) if(f[i]>ma) ma=f[i],u=i;
    cout<<"! "<<ma<<' ';
    print(u);
    cout<<endl;
}

Comments

No comments yet.