交互题解题报告

文章目录

      • 交互格式
        • IO交互
        • grader交互
        • 其他注意事项
      • 交互题的本地测试
        • 准备
        • IO交互测试
        • grader交互测试
      • 题解
        • 猜数字2
        • 拉面比较
          • 20pts: N ≤ 30 N\leq 30 N30
          • 50pts: N ≤ 300 N\leq 300 N300
          • 100pts: N ≤ 400 N\leq 400 N400
        • 「JOISC 2017 Day 3」自然公园
          • task1 10pts: N ≤ 250 N\leq 250 N250
          • task2 10pts: M = N − 1 M=N-1 M=N1,图是一条链,且 0 0 0 N − 1 N-1 N1为端点。
          • task3 27pts: M = N − 1 M=N-1 M=N1,图是树,深度 l o g log log级别
          • task4 30pts: M = N − 1 M=N-1 M=N1
          • task5 23pts: 无特殊限制
        • 「WC2019」I 君的商店
          • task 1,2,4,5 60pts:
          • task3 9pts:
          • task6 31pts:
        • [NOI2019] I君的探险

交互格式

交互题一般分为两种:IO交互grader交互

IO交互

需要包含对应的交互库头文件。
一般题目会在题面定义好每种操作的格式,选手通过打印对应的字符串进行交互。
每次输出后要即时清空缓存,保证交互正常进行。
对于cout,endl时会自动清除缓存。
对于printf,可以使用 fflush(stdout) 清除缓存。
例题:猜数字2

grader交互

需要包含对应的交互库头文件。
题目一般会给出几个函数,有一些是交互库中已经写好的,一些是需要自己实现的,选手不需要定义已经写好的代码,只需要按照题目格式定义并编写要求选手实现的代码即可。
不应该包含main函数。
例题:拉面比较

其他注意事项

如果OJ或者题目有特殊要求,优先遵循其要求。如:luogu要求交互题必须不包含交互库头文件,对于grader交互题必须对所有给出函数进行定义。

交互题的本地测试

准备

题目一般会给出用于交互评测的代码以及交互库头文件,需提前下载好。

IO交互测试

貌似只能本地模拟。

grader交互测试

这里环境为linux。
假设交互评测的代码为grader.cpp,选手的代码为U1.cpp
进入终端输入命令g++ -O2 -o work grader.cpp U1.cpp,其中work为最终生成的程序的名字,可以自行定义。
./work运行程序,按照评测代码的输入格式输入数据,即可进行交互并打印评测结果。

题解

猜数字2

题目

交互的模板题,二分猜即可。
注意交互格式,及时清除缓存。

参考代码:

#include
#include"interaction.h"
using namespace std;
const int N=110;
int n,a[N];
int guess(int R){
    int l=1,r=1e6,mid,ans=0,tmp;
    while(l+1<r){
        mid=(l+r)>>1;
        printf("guess %d %d\n",R,mid);
        fflush(stdout);
        scanf("%d",&tmp);
        if(tmp==0) return mid;
        else if(tmp==-1) l=mid;
        else r=mid;
    }
    printf("guess %d %d\n",R,l);
    fflush(stdout);
    scanf("%d",&tmp);
    if(tmp==0) return l;
    return r;
}
int main(){
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
    printf("get_num\n");
    fflush(stdout);
    scanf("%d",&n);
    for(int i=0;i<n;i++) a[i]=guess(i);
    printf("submit");
    for(int i=0;i<n;i++) printf(" %d",a[i]);
    printf("\n");
    return 0;
}

拉面比较

20pts: N ≤ 30 N\leq 30 N30

直接进行两两比较,比较次数上限为 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n1)

50pts: N ≤ 300 N\leq 300 N300

扫一遍序列,并记录前 i i i个元素的最大值和最小值的位置,每次只和最大值/最小值比较即可。
比较次数上限为 2 n − 2 2n-2 2n2

100pts: N ≤ 400 N\leq 400 N400

先每两个元素分成一组,进行 ⌊ n 2 ⌋ \lfloor\frac{n}{2}\rfloor 2n
次比较,将较小数分到一个集合 A A A,较大数分到几个集合 B B B,运用50pts的算法对 A A A求最小值,对 B B B求最大值,两个过程各需 ⌈ n 2 ⌉ − 1 \lceil\frac{n}{2}\rceil-1 2n1次比较。
比较次数上限为 ⌈ 3 n 2 ⌉ − 2 \lceil\frac{3n}{2}\rceil-2 23n2

参考代码:

#include
#include"ramen.h"
using namespace std;
vector<int> biger,smler;
//int Compare(int X,int Y);
//int Answer(int X,int Y);
void Ramen(int n){
    if(n==1){Answer(0,0);return;}
    for(int i=1;i<n;i+=2){
        int tmp=Compare(i-1,i);
        if(tmp==1) biger.push_back(i-1),smler.push_back(i);
        else biger.push_back(i),smler.push_back(i-1);
    }
    if(n&1) biger.push_back(n-1),smler.push_back(n-1);
    int mx=biger[0];
    for(int i=1;i<biger.size();i++){
        if(Compare(mx,biger[i])<0) mx=biger[i];
    }
    int mn=smler[0];
    for(int i=1;i<smler.size();i++){
        if(Compare(mn,smler[i])>0) mn=smler[i];
    }
    Answer(mn,mx);
}
//int main(){
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
//
//    return 0;
//}

「JOISC 2017 Day 3」自然公园

题目

task1 10pts: N ≤ 250 N\leq 250 N250

暴力枚举两个点,检查能否直接从一个点到另一个点,有则两点存在连边。

namespace task1{
    void sol(){
        for(int i=0;i<n;i++)
            for(int j=i+1;j<n;j++){
                for(int k=0;k<n;k++){
                    if(k==i||k==j) pla[k]=1;
                    else pla[k]=0;
                }
                if(Ask(i,j,pla)) Answer(i,j);
            }
    }
}
task2 10pts: M = N − 1 M=N-1 M=N1,图是一条链,且 0 0 0 N − 1 N-1 N1为端点。

考虑整体二分。
每次递归维护一个序列,左右端点固定,从中间随机一个点作为分界点。
先将序列里所有点置成 1 1 1,从左到右枚举点,尝试将该点状态设成0,检查 Ask(l,mid) 是否为真,若为真说明该点在右区间,否则在左区间。

namespace task2{
    void Div(vector<int> vec){
        if(vec.size()==2) return report(vec[0],vec[1]),void();
        int l=vec[0],r=vec.back(),mid=vec[rnd()%(vec.size()-2)+1];
        vector<int> L,R;L.push_back(l);R.push_back(mid);
        for(int p:vec) pla[p]=1;
        for(int i=1;i<(int)vec.size()-1;i++){
            if(vec[i]==mid) continue;
            pla[vec[i]]=0;
            if(qry(l,mid,pla)) R.push_back(vec[i]);
            else L.push_back(vec[i]);
            pla[vec[i]]=1;
        }
        L.push_back(mid);R.push_back(r);
        for(int p:vec) pla[p]=0;
        Div(L);Div(R);
    }
    void sol(){
        vector<int> vec;
        for(int i=0;i<n;i++) vec.push_back(i);
        shuffle(vec.begin()+1,vec.end()-1,rnd);//at least two points,don't worry about RE
        Div(vec);
    }
}
task3 27pts: M = N − 1 M=N-1 M=N1,图是树,深度 l o g log log级别

考虑以 0 0 0为根,从上到下一层一层拓展,找到每个点的父亲,各自与其父亲连边。
对于每一层拓展,维护上一次新拓展的一层点(该层记为 Q Q Q),对于当前考虑要拓展的一层 A A A,进行整体二分。在 Q Q Q中取 m i d mid mid m i d mid mid左侧置1, m i d mid mid右侧置0,检查每一个在 A A A的点 i i i,若 Ask(0,i) 为真,说明该点父亲在左侧区间,否则在右侧区间。
每一轮整体二分完毕后,将 Q Q Q永久置为 1 1 1,将 A A A作为新的 Q Q Q。将 Q Q Q置为1,枚举剩余未拓展的点,若该点 Ask(0,i) 为真,则该点在 A A A层。找到所有 A A A层点后继续新一轮整体二分。

namespace task3{
    int pre[N],stk[N],fa[N],lq[N],rq[N],cnt=0,pretop=0;bool vis[N];
    void Div(int l,int r,int ql,int qr){
        if(l>r||ql>qr) return ;
        if(l==r){
            for(int i=ql;i<=qr;i++) fa[stk[i]]=pre[l];
            return ;
        }
        int mid=(l+r)>>1,lt=0,rt=0;
        for(int i=l;i<=mid;i++) pla[pre[i]]=1;
        for(int i=mid+1;i<=r;i++) pla[pre[i]]=0;
        for(int i=ql;i<=qr;i++){
            pla[stk[i]]=1;
            if(qry(0,stk[i],pla)) lq[++lt]=stk[i];
            else rq[++rt]=stk[i];
            pla[stk[i]]=0;
        }
        for(int i=l;i<=mid;i++) pla[pre[i]]=0;
        for(int i=1;i<=lt;i++) stk[ql+i-1]=lq[i];
        for(int i=1;i<=rt;i++) stk[ql+lt+i-1]=rq[i];
        Div(l,mid,ql,ql+lt-1);
        Div(mid+1,r,ql+lt,qr);
    }
    void work(){
        int top=0;
        for(int i=0;i<n;i++){
            if(vis[i]) continue;
            pla[i]=1;
            if(qry(0,i,pla)){
                cnt++;stk[++top]=i;
            }
            pla[i]=0;
        }
        Div(1,pretop,1,top);
        for(int i=1;i<=pretop;i++) pla[pre[i]]=1;
        for(int i=1;i<=top;i++) vis[stk[i]]=1,pla[stk[i]]=1,pre[i]=stk[i];
        pretop=top;
        if(cnt==n) return;
        else work();
    }
    void sol(){
        cnt=0;pla[0]=cnt=vis[0]=1;
        pre[pretop=1]=0;
        work();
        for(int i=1;i<n;i++) report(fa[i],i);
    }
}
task4 30pts: M = N − 1 M=N-1 M=N1

注意到图仍然是树。
维护一个序列,初始为 0 0 0~ N − 1 N-1 N1,随机打乱。

  1. 取序列首端和末端为端点,在图中找一条链。对于找链,先将序列里所有点置 1 1 1,然后枚举点尝试置为 0 0 0,若置 0 0 0后左右端点不再连通,则该点在链上。
  2. 找到链后,将链作为当前连通块的一个抽象的根,做类似于task3的方法判断除链外剩余点的祖先是链上的哪一个点。将剩余点总集合按照祖先分为链长个集合,每一个集合成为一个新的独立的连通块。
  3. 对于操作 2 2 2产生的每个独立的新连通块,进行操作 1 1 1。以此类推直到序列大小小于等于 2 2 2,若序列大小为 2 2 2,两点直接连边,结束。
namespace task4{
    vector<vector<int> > pot;
    vector<int> Div(vector<int> vec){
        if(vec.size()==2) return report(vec[0],vec[1]),vec;
        int l=vec[0],r=vec.back(),mid=vec[rnd()%(vec.size()-2)+1];
        vector<int> L,R;
        L.push_back(vec[0]);R.push_back(mid);
        for(int p:vec) pla[p]=1;
        for(int i=1;i<(int)vec.size()-1;i++){
            if(vec[i]==mid) continue;
            pla[vec[i]]=0;
            if(!qry(l,mid,pla)) L.push_back(vec[i]);
            else R.push_back(vec[i]);
            pla[vec[i]]=1;
        }
        L.push_back(mid);R.push_back(r);
        for(int p:vec) pla[p]=0;
        L=Div(L);R=Div(R);
        L.pop_back();
        for(int p:R) L.push_back(p);
        return L;
    }
    void solve(int l,int r,vector<int> &chain,vector<int> S){
        if(l==r){pot[l]=S;return ;}
        for(int p:S) pla[p]=1;
        int mid=(l+r)>>1;
        for(int i=l;i<=mid;i++) pla[chain[i]]=1;
        vector<int> L,R;
        for(int p:S){
            if(qry(p,chain[l],pla)) L.push_back(p);
            else R.push_back(p);
        }
        for(int i=l;i<=mid;i++) pla[chain[i]]=0;
        for(int p:S) pla[p]=0;
        solve(l,mid,chain,L);
        solve(mid+1,r,chain,R);
    }
    void Solve(vector<int> vec){
        if(vec.size()==1) return ;
        if(vec.size()==2) return report(vec[0],vec[1]),void();
        vector<int> chain;chain.push_back(vec[0]);
        for(int p:vec) pla[p]=1;
        for(int i=1;i<(int)vec.size()-1;i++){
            pla[vec[i]]=0;
            if(!qry(vec[0],vec.back(),pla)) chain.push_back(vec[i]);
            pla[vec[i]]=1;
        }
        for(int p:vec) pla[p]=0;
        chain.push_back(vec.back());
        chain=Div(chain);
        pot.clear();
        pot.resize(chain.size());
        vector<int> S;bool vis[N];
        memset(vis,0,sizeof vis);
        for(int p:chain) vis[p]=1;
        for(int p:vec) if(!vis[p]) S.push_back(p);
        for(int p:chain) vis[p]=0;
        solve(0,chain.size()-1,chain,S);
        vector<vector<int> > tmppot=pot;
        for(int i=0;i<(int)tmppot.size();i++){
            tmppot[i].push_back(chain[i]);
            Solve(tmppot[i]);
        }

    }
    void sol(){
        vector<int> vec;
        for(int i=0;i<n;i++) vec.push_back(i);
        shuffle(vec.begin(),vec.end(),rnd);
        Solve(vec);
    }
}
task5 23pts: 无特殊限制

考虑维护一个连通块,初始时连通块即为 0 0 0号点,大小为 1 1 1

  1. 判断该点 x x x是否与连通块直接相连(即与块中某个点通过某条边直接相连),若没有直接相连,则套路的使用二分前缀法随机取一个在连通块和该点 x x x之间路径上的一个点 y y y,对 y y y执行操作 1 1 1直到 x x x和块直接相连;当 x x x已经满足和连通块直接相连时,执行操作 2 2 2
  2. 对于当前的连通块,从块中一个出发点 z z z开始通过已经找到的边向外bfs,并将bfs的所有点置 1 1 1,检查此时点 x x x是否与该出发点 z z z连通,若不连通,初始化所有点状态为 0 0 0,并结束;否则使用二分前缀法找到当前连通块中序最小的和 x x x直接相连的点 y y yreport并给点 y y y打上删除标记(下一次通过操作 1 1 1执行操作 2 2 2时,之前打的删除标记无效),将原连通块分割成若干连通块,枚举 y y y的出边,向其出边指向的连通块递归地进行操作 2 2 2。当子连通块的递归结束,回溯到该层时,将这一层发现的 x → z x\rightarrow z xz的连边存储到邻接表中。结束。
namespace task5{
    int tim=0,eratim=0,col[N],era[N],q[N],tmp[N];bool vis[N];
    vector<int> E[N];
    void link(int x,int y){
        E[x].push_back(y);
        E[y].push_back(x);
    }
    void Div(int x,int z){
        ++tim;col[x]=tim;
        int hh=1,tt=1;q[tt]=x;
        while(hh<=tt){
            int p=q[hh++];
            for(int to:E[p])
                if(col[to]!=tim&&era[to]!=eratim){
                    col[to]=tim;
                    q[++tt]=to;
                }
        }
        for(int i=1;i<=tt;i++) pla[q[i]]=1;
        if(!qry(x,z,pla)){
            for(int i=1;i<=tt;i++) pla[q[i]]=0;
            return ;
        }
        int b=tt,l=1,r=tt,mid;
        while(l!=r){
            mid=(l+r)>>1;
            for(;b<mid;b++) pla[q[b+1]]=1;
            for(;b>mid;b--) pla[q[b]]=0;
            if(qry(x,z,pla)) r=mid;
            else l=mid+1;
        }
        for(;b;b--) pla[q[b]]=0;
        int y=q[l];
        report(y,z);era[y]=eratim;
        for(int p:E[y])
            if(era[p]!=eratim)
                Div(p,z);
        link(y,z);
    }
    void Find(int x){
        ++eratim;
        pla[x]=1;
        Div(0,x);
        pla[x]=0;
    }
    void gt(int x){
        int len=0;
        for(int i=0;i<n;i++){
            if(!vis[i]&&i!=x) tmp[++len]=i;
            else pla[i]=1;
        }
        if(qry(0,x,pla)){
            memset(pla,0,sizeof(int)*n);
            Find(x);
            vis[x]=1;
            return ;
        }
        else{
            int b=0,l=1,r=len,mid;
            while(l!=r){
                mid=(l+r)>>1;
                for(;b<mid;b++) pla[tmp[b+1]]=1;
                for(;b>mid;b--) pla[tmp[b]]=0;
                if(qry(0,x,pla)) r=mid;
                else l=mid+1;
            }
            for(;b;b--) pla[tmp[b]]=0;
            int y=tmp[l];
            vis[x]=1;
            gt(y);
            vis[x]=0;
            gt(x);
        }
    }
    void sol(){
        vis[0]=1;
        vector<int> vec;
        for(int i=0;i<n;i++) vec.push_back(i);
        shuffle(vec.begin(),vec.end(),rnd);
        for(int p:vec){
            if(!vis[p]){
                gt(p);
            }
        }
    }
}

代码全貌如下:

#include
#include"park.h"
using namespace std;
//void Ask(int x,int y,int a[]);void Answer(int x,int y);
const int N=1450;
mt19937 rnd(19260817);
int pla[N],n;
int qry(int i,int j,int tmp[]){return Ask(min(i,j),max(i,j),tmp);}
void report(int i,int j){Answer(min(i,j),max(i,j));}
namespace task1{
    void sol(){
        for(int i=0;i<n;i++)
            for(int j=i+1;j<n;j++){
                for(int k=0;k<n;k++){
                    if(k==i||k==j) pla[k]=1;
                    else pla[k]=0;
                }
                if(Ask(i,j,pla)) Answer(i,j);
            }
    }
}
namespace task2{
    void Div(vector<int> vec){
        if(vec.size()==2) return report(vec[0],vec[1]),void();
        int l=vec[0],r=vec.back(),mid=vec[rnd()%(vec.size()-2)+1];
        vector<int> L,R;L.push_back(l);R.push_back(mid);
        for(int p:vec) pla[p]=1;
        for(int i=1;i<(int)vec.size()-1;i++){
            if(vec[i]==mid) continue;
            pla[vec[i]]=0;
            if(qry(l,mid,pla)) R.push_back(vec[i]);
            else L.push_back(vec[i]);
            pla[vec[i]]=1;
        }
        L.push_back(mid);R.push_back(r);
        for(int p:vec) pla[p]=0;
        Div(L);Div(R);
    }
    void sol(){
        vector<int> vec;
        for(int i=0;i<n;i++) vec.push_back(i);
        shuffle(vec.begin()+1,vec.end()-1,rnd);//at least two points,don't worry about RE
        Div(vec);
    }
}
namespace task3{
    int pre[N],stk[N],fa[N],lq[N],rq[N],cnt=0,pretop=0;bool vis[N];
    void Div(int l,int r,int ql,int qr){
        if(l>r||ql>qr) return ;
        if(l==r){
            for(int i=ql;i<=qr;i++) fa[stk[i]]=pre[l];
            return ;
        }
        int mid=(l+r)>>1,lt=0,rt=0;
        for(int i=l;i<=mid;i++) pla[pre[i]]=1;
        for(int i=mid+1;i<=r;i++) pla[pre[i]]=0;
        for(int i=ql;i<=qr;i++){
            pla[stk[i]]=1;
            if(qry(0,stk[i],pla)) lq[++lt]=stk[i];
            else rq[++rt]=stk[i];
            pla[stk[i]]=0;
        }
        for(int i=l;i<=mid;i++) pla[pre[i]]=0;
        for(int i=1;i<=lt;i++) stk[ql+i-1]=lq[i];
        for(int i=1;i<=rt;i++) stk[ql+lt+i-1]=rq[i];
        Div(l,mid,ql,ql+lt-1);
        Div(mid+1,r,ql+lt,qr);
    }
    void work(){
        int top=0;
        for(int i=0;i<n;i++){
            if(vis[i]) continue;
            pla[i]=1;
            if(qry(0,i,pla)){
                cnt++;stk[++top]=i;
            }
            pla[i]=0;
        }
        Div(1,pretop,1,top);
        for(int i=1;i<=pretop;i++) pla[pre[i]]=1;
        for(int i=1;i<=top;i++) vis[stk[i]]=1,pla[stk[i]]=1,pre[i]=stk[i];
        pretop=top;
        if(cnt==n) return;
        else work();
    }
    void sol(){
        cnt=0;pla[0]=cnt=vis[0]=1;
        pre[pretop=1]=0;
        work();
        for(int i=1;i<n;i++) report(fa[i],i);
    }
}
namespace task4{
    vector<vector<int> > pot;
    vector<int> Div(vector<int> vec){
        if(vec.size()==2) return report(vec[0],vec[1]),vec;
        int l=vec[0],r=vec.back(),mid=vec[rnd()%(vec.size()-2)+1];
        vector<int> L,R;
        L.push_back(vec[0]);R.push_back(mid);
        for(int p:vec) pla[p]=1;
        for(int i=1;i<(int)vec.size()-1;i++){
            if(vec[i]==mid) continue;
            pla[vec[i]]=0;
            if(!qry(l,mid,pla)) L.push_back(vec[i]);
            else R.push_back(vec[i]);
            pla[vec[i]]=1;
        }
        L.push_back(mid);R.push_back(r);
        for(int p:vec) pla[p]=0;
        L=Div(L);R=Div(R);
        L.pop_back();
        for(int p:R) L.push_back(p);
        return L;
    }
    void solve(int l,int r,vector<int> &chain,vector<int> S){
        if(l==r){pot[l]=S;return ;}
        for(int p:S) pla[p]=1;
        int mid=(l+r)>>1;
        for(int i=l;i<=mid;i++) pla[chain[i]]=1;
        vector<int> L,R;
        for(int p:S){
            if(qry(p,chain[l],pla)) L.push_back(p);
            else R.push_back(p);
        }
        for(int i=l;i<=mid;i++) pla[chain[i]]=0;
        for(int p:S) pla[p]=0;
        solve(l,mid,chain,L);
        solve(mid+1,r,chain,R);
    }
    void Solve(vector<int> vec){
        if(vec.size()==1) return ;
        if(vec.size()==2) return report(vec[0],vec[1]),void();
        vector<int> chain;chain.push_back(vec[0]);
        for(int p:vec) pla[p]=1;
        for(int i=1;i<(int)vec.size()-1;i++){
            pla[vec[i]]=0;
            if(!qry(vec[0],vec.back(),pla)) chain.push_back(vec[i]);
            pla[vec[i]]=1;
        }
        for(int p:vec) pla[p]=0;
        chain.push_back(vec.back());
        chain=Div(chain);
        pot.clear();
        pot.resize(chain.size());
        vector<int> S;bool vis[N];
        memset(vis,0,sizeof vis);
        for(int p:chain) vis[p]=1;
        for(int p:vec) if(!vis[p]) S.push_back(p);
        for(int p:chain) vis[p]=0;
        solve(0,chain.size()-1,chain,S);
        vector<vector<int> > tmppot=pot;
        for(int i=0;i<(int)tmppot.size();i++){
            tmppot[i].push_back(chain[i]);
            Solve(tmppot[i]);
        }

    }
    void sol(){
        vector<int> vec;
        for(int i=0;i<n;i++) vec.push_back(i);
        shuffle(vec.begin(),vec.end(),rnd);
        Solve(vec);
    }
}
namespace task5{
    int tim=0,eratim=0,col[N],era[N],q[N],tmp[N];bool vis[N];
    vector<int> E[N];
    void link(int x,int y){
        E[x].push_back(y);
        E[y].push_back(x);
    }
    void Div(int x,int z){
        ++tim;col[x]=tim;
        int hh=1,tt=1;q[tt]=x;
        while(hh<=tt){
            int p=q[hh++];
            for(int to:E[p])
                if(col[to]!=tim&&era[to]!=eratim){
                    col[to]=tim;
                    q[++tt]=to;
                }
        }
        for(int i=1;i<=tt;i++) pla[q[i]]=1;
        if(!qry(x,z,pla)){
            for(int i=1;i<=tt;i++) pla[q[i]]=0;
            return ;
        }
        int b=tt,l=1,r=tt,mid;
        while(l!=r){
            mid=(l+r)>>1;
            for(;b<mid;b++) pla[q[b+1]]=1;
            for(;b>mid;b--) pla[q[b]]=0;
            if(qry(x,z,pla)) r=mid;
            else l=mid+1;
        }
        for(;b;b--) pla[q[b]]=0;
        int y=q[l];
        report(y,z);era[y]=eratim;
        for(int p:E[y])
            if(era[p]!=eratim)
                Div(p,z);
        link(y,z);
    }
    void Find(int x){
        ++eratim;
        pla[x]=1;
        Div(0,x);
        pla[x]=0;
    }
    void gt(int x){
        int len=0;
        for(int i=0;i<n;i++){
            if(!vis[i]&&i!=x) tmp[++len]=i;
            else pla[i]=1;
        }
        if(qry(0,x,pla)){
            memset(pla,0,sizeof(int)*n);
            Find(x);
            vis[x]=1;
            return ;
        }
        else{
            int b=0,l=1,r=len,mid;
            while(l!=r){
                mid=(l+r)>>1;
                for(;b<mid;b++) pla[tmp[b+1]]=1;
                for(;b>mid;b--) pla[tmp[b]]=0;
                if(qry(0,x,pla)) r=mid;
                else l=mid+1;
            }
            for(;b;b--) pla[tmp[b]]=0;
            int y=tmp[l];
            vis[x]=1;
            gt(y);
            vis[x]=0;
            gt(x);
        }
    }
    void sol(){
        vis[0]=1;
        vector<int> vec;
        for(int i=0;i<n;i++) vec.push_back(i);
        shuffle(vec.begin(),vec.end(),rnd);
        for(int p:vec){
            if(!vis[p]){
                gt(p);
            }
        }
    }
}
void Detect(int T,int N){
    n=N;
    if(T==1){task1::sol();return ;}
    if(T==2){task2::sol();return ;}
    if(T==3){task3::sol();return ;}
    if(T==4){task4::sol();return ;}
    if(T==5){task5::sol();return ;}
}
//int main(){
    freopen(".in","r",stdin);
    freopen(".out","w",stdout);
//
//    return 0;
//}

「WC2019」I 君的商店

明确一点,query()=0等价于"大于等于",query()=1等价与"小于等于"。

task 1,2,4,5 60pts:

显然我们可以用 O ( 2 n ) O(2n) O(2n)的操作得到一个确定的 1 1 1的位置,从左到有枚举一遍,取最大值的下标即可,记为 z z z
然后每次从剩余的数中取两个数 x x x y y y( x ≤ y x\leq y xy),若 x + y ≤ z x+y\leq z x+yz a n s [ x ] = 0 ans[x]=0 ans[x]=0;若 x + y ≥ z x+y\geq z x+yz,则 a n s [ y ] = 1 ans[y]=1 ans[y]=1
最后剩余一个数根据 1 1 1的个数的奇偶性判断即可。

操作复杂度: O ( 7 n ) O(7n) O(7n)

task3 9pts:

根据限制显然整个 a n s ans ans序列必然是一段若干长度的 0 0 0和一段长度大于等于一的 1 1 1,或者一段长度大于等于一的 1 1 1和一段若干长度的 0 0 0,即000…111或111…000。
考虑二分这个分界点。
可以通过比较两端点找到 1 1 1在哪一侧,然后通过比较 { m i d , m i d + 1 } \{mid,mid+1\} {mid,mid+1} 1 1 1的大小关系即可二分分界点。
通过 1 1 1的个数的奇偶性可以判断分界点具体的数值。

操作复杂度: O ( 3 l o g n ) O(3logn) O(3logn)

task6 31pts:

考虑任取一个点 z z z,每次从剩余点中取两个数 x x x y ( x ≤ y ) y(x\leq y) y(xy)

  1. x + y ≤ z x+y\leq z x+yz a n s [ x ] = 0 ans[x]=0 ans[x]=0;
  2. x + y ≥ z x+y\geq z x+yz y ≥ z y\geq z yz,将当前 z z z放入一个栈中,将当前 y y y作为新的 z z z

做完后将 x x x z z z中较小的作为 x x x,较大的作为 z z z。那么此时 z z z必然为全局最大值,即 1 1 1
z z z也插入栈中,则此时栈内从底部到顶部数值递增,可以对栈内进行task3的二分。
这样还剩余 x x x和栈内的分界点(这里设为 y y y)未确定数值,将 x + y x+y x+y 1 1 1比较:若 x + y ≤ 1 x+y\leq 1 x+y1 a n s [ x ] = 0 ans[x]=0 ans[x]=0;若 x + y ≥ 1 x+y\geq 1 x+y1,则 a n s [ y ] = 1 ans[y]=1 ans[y]=1。剩余的一个点根据 1 1 1的个数的奇偶性判断即可。

操作复杂度: O ( 5 n + 3 l o g n ) O(5n+3logn) O(5n+3logn)

具体细节参考代码:

#include
#include"shop.h"
using namespace std;
const int N=1e5+10;
int n,K,tmp[N],stk[N],top=0;
int Ask1(int x,int y){
    int _A[]={x},_B[]={y};
    return query(_A,1,_B,1);
}
int Ask2(int x,int y,int z){
    int _A[]={x,y},_B[]={z};
    return query(_A,2,_B,1);
}
void swp(int &x,int &y){
    if(!Ask1(x,y)) swap(x,y);
}
void find_price(int task_id,int _N,int _K,int ans[]){
    n=_N;K=_K;
    for(int i=0;i<n;i++) ans[i]=0;
    if(n==1) return ans[0]=1,void();
    if(n==2){
        int p=Ask1(0,1);
        ans[p]=ans[p^1^K]=1;
        return ;
    }
    if(task_id==1||task_id==2||task_id==4||task_id==5){
        for(int i=0;i<n;i++) ans[i]=-1;
        int mx=0;
        for(int i=1;i<n;i++) if(Ask1(mx,i)) mx=i;
        ans[mx]=1;
        int a=0,b=1,flag=1;
        for(int i=1;i<n-1;i++){
            while(a==b||a==mx) a++;
            while(a==b||b==mx) b++;
            if(!Ask1(a,b)) swap(a,b);
            if(Ask2(a,b,mx)) ans[a]=0,a=max(a,b)+1;
            else ans[b]=1,b=max(a,b)+1,flag^=1;
        }
        if(ans[a]==-1) ans[a]=flag^K;
        else ans[b]=flag^K;
        return ;
    }
    if(task_id==3){
        for(int i=0;i<n;i++) tmp[i]=i;
        if(Ask1(0,n-1)) reverse(tmp,tmp+n);
        int l=0,r=n-2,mid;
        while(l+1<r){
            mid=(l+r)>>1;
            if(!Ask2(tmp[mid],tmp[mid+1],tmp[0])) l=mid;
            else r=mid;
        }
        if(((l+1)&1)!=K) l++;
        for(int i=0;i<=l;i++) ans[tmp[i]]=1;
        for(int i=l+1;i<n;i++) ans[tmp[i]]=0;
        return ;
    }
    if(task_id==6){
        int x=0,y,z=1;
        for(int i=2;i<n;i++){
            y=i;swp(x,y);
            if(Ask2(x,y,z)){ans[x]=0;x=y;}
            else{stk[++top]=z;z=y;}
        }
        swp(x,z);ans[z]=1;
        if(!top){ans[x]=(K^1);return ;}
        stk[++top]=z;
        int l=1,r=top-1,mid;
        while(l<r){
            mid=(l+r)>>1;
            if(Ask2(stk[mid],stk[mid+1],z)) l=mid+1;
            else r=mid;
        }
        for(int i=1;i<l;i++) ans[stk[i]]=0;
        for(int i=l+1;i<=top;i++) ans[stk[i]]=1;
        y=stk[l];int flag=((top-l)&1)^K;
        swp(x,y);
        if(Ask2(x,y,z)){ans[x]=0;ans[y]=flag;}
        else{
            ans[y]=1;ans[x]=flag^1;
        }
        return ;
    }
}
//int main(){
    freopen(".in","r",stdin);
    freopen(".out","w",stdout);
//
//    return 0;
//}

[NOI2019] I君的探险

[NOI2019]I君的探险

你可能感兴趣的:(解题报告,c++)