I. 西行妖下题解

题目内容

原题链接


样例输入

5

2

0

1

样例输出

? 4 4 2 3 2

? 3 5 1 5 5

? 5 2 4 3 1

! 3 2 1 5 4

题解

首先,题目有一个很难解决的痛点,就是他只会返回最前面的那个下标。那我们就不妨倒着做,从后往前处理。
先考虑最后一个数字。我们如果发出类似 "1 1 1 1 2" 这样的询问,就相当于把最后一个数字抬高了 \(1\), 相应地,改成 "1 1 1 1 3" 就是把最后一个数字抬高了 \(2\)
我们先不断抬高最后一个数字,期间也记录下来他和前面某些数字的关系(前面这个数字比最后一位大 \(k\)),这样我们在知道最后一个数字之后就能直接推出有关系的数字,减少询问次数。

可以发现,最后一个数字被抬高某个 \(k\) 之后,交互器一定会返回 \(0\),因为此时他比最大值 \(n\) 大了 \(1\)。那么这个时候,我们就找出来了最大值 \(n\) 的位置以及最后一个数字的值。(二者当然有可能重合)。
在这里,假设我们花了 \(M\) 次询问,那么这 \(M\) 次询问也一定会让我们知道 \(M\) 个值,所以没有超出一个数字平均两次询问机会的限制。

接着将指针前移。如果某个数字已经被确定,就略过他。直到某个未确认数字为止。类似地,我们不断抬高这个数字。先是获得了对他前面某些数字的关系,一直到交互器返回他自己的下标。注意,此时他身后的值都已经确定。那么这里就一定是他和他身后的最小值重合。(注意,他的身后不可能出现已经被确定的比当期数字小的值。因为若比他小的数字被确定,则当前数字也一定会在后面数字被不断抬高时确定。)所以直接记录最小值然后减掉抬高的部分便推出当前数字,同时也推出与他有关系的部分。

等到最后一个空余位置时,可以直接排除出他的值,进一步减少询问次数。

下面考虑上述操作询问次数的合法性。可以发现,对于“ 被关系推出来”的数字,他们消耗的次数都是 \(1\)。而对于需要我们主动确定(被抬升的数),他们也都是在询问至边界时被直接确定。所以上述流程的询问次数是 \(N\) 次,远小于题目给的限制 \(2 * N\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define N 100010

int n; 
int ans[N]; 

vector <pair<int, pair<int, int> > > G; 

int main(){
    memset(ans, 0, sizeof(ans)); 
    cin >> n; 
    int del = 1; 
    int tmp = 0; 
    int last = n; 
    while(666){
        printf("? "); 
        for(int i = 1; i < n; i++){
            printf("1 "); 
        }
        printf("%d\n", 1 + del);
		fflush(stdout);
        last = tmp;  
        scanf("%d", &tmp); 
         
        if(tmp != 0){
            G.push_back(make_pair(del, make_pair(n, tmp))); 
        } 
           
        else break; 
        del++; 
    }
    ans[last] = n; 
    ans[n] = n - del + 1; 
    for(auto it  : G){
        pair<int, int> t = it.second; 
        ans[t.second] = ans[t.first] + it.first; 
    }
    G.clear(); 

    int minn = 1e9; 

    for(int i = n; i >= 2; i--){
        if(ans[i]){
            minn = min(minn, ans[i]);
            continue; 
        }
        del = 1; 
        while(666){
            printf("? "); 
            for(int j = 1; j <= n; j++){
                if(j == i) printf("%d ", 1 + del); 
                else printf("1 "); 
            }
            printf("\n"); 
			fflush(stdout);
            scanf("%d", &tmp); 
             

            if(tmp == 0) break; 
            else if(tmp != i) G.push_back(make_pair(del, make_pair(i, tmp))); 
            else if(tmp == i){
                break; 
            }
            del++;  
        }
        if(tmp == 0) ans[i] = n - del + 1; 
        else if(tmp == i) ans[i] = minn - del; 
        minn = min(minn, ans[i]); 
        for(auto it : G){
            pair<int, int> t = it.second; 
            ans[t.second] = ans[t.first] + it.first; 
        }
        G.clear(); 
    }
    bool vis[1001]; 
    memset(vis, 0, sizeof(vis)); 
    for(int i = 2; i <= n; i++)
    	vis[ans[i]] = 1;
    for(int i = 1; i <= n; i++)
    	if(!vis[i]){
    		ans[1] = i;
    		break; 
		}
    printf("! ");
    for(int i = 1; i <= n; i++)
        printf("%d ", ans[i]);
    printf("\n"); 
    fflush(stdout);
    return 0; 
}
posted @ 2023-01-06 15:53  雪之下,树之旁  阅读(20)  评论(0编辑  收藏  举报