AT_ARC70_D 解题报告
前言
传送门 先满上!
考试考了这个题目挺难泵的
主要还是 Ad-hoc 个人认为还是没有黑只有紫的
挺难想的,如果秒了肯定是 CF 巨佬
将同步发布于 luogu博客,不肯定拥有更好的阅读体验
题目意思
有 \(n\) 个人,其中 \(A\) 个人是诚实的,\(B\) 个人是不诚实的,满足 \(A+B=n\),你可以进行不超过 \(2*n\) 次操作,每一次操作是对于一个有序二元组 \((x,y)\) 询问,如果 \(x\) 是诚实的,他就会如实告诉你 \(y\) 的身份,否则的话 \(x\) 会告诉你一个随机的值,需要判断所有人的身份以及是否无解
思路
首先我们不思考 \(2*n\) 的做法,有点太难以应对了,先想一想无解和 \(n^2\) 怎么做,通过样例和考场发的大样例以及手玩后的一些情况猜测无解就是 \(A\le B\),思考发现是对的,因为不诚实的人提供的信息是无用的,如果我们有更多的诚实的人,就能知道更多的信息,如果诚实的人比不诚实的人多,就不会被不诚实的人误导。
所以能知道一个 \(n^2\) 的做法,对于每一个人,让所有的 \(n\) 个人都问他的身份,取众数就一定是他的身份。
思考这个做法的精髓在哪里,就是通过诚实的人和不诚实的人互相单挑把一个不诚实的人的干扰取消掉,相当于用一个诚实的人换掉了一个不诚实的人,因为诚实的人比不诚实的人多,所以最后剩下的一定是诚实的人。
上面可能有一些拗口,但是我也止步于此了。
继续顺着精髓往后思考,我们设 \(0\) 表示不诚实,\(1\) 表示诚实,如果发现了一个诚实的人就没有必要继续往后找了,直接推平即可,那么被指控是不诚实的情况只有三种 \({(0,0)},{(0,1)},{(1,0)}\),那么发现这三种情况中诚实的人是不多于不诚实的人的,所以将这两个人都踢掉一定不劣,因为此时我们希望不诚实的人更少,留下一些诚实的人,所以可以维护一个栈表示目前没有踢掉的人,那么每一次踢掉就一定可以踢掉至少一个不诚实的人,最后不诚实的人踢完了,就只会剩下诚实的人留在栈中,为什么诚实的人一定会留在栈里面呢,因为诚实的人比不诚实的人多,所以每一次至少一换一,最后一定会留下诚实的人。接下来计算时间复杂度,我们栈顶的运算有 \(O(n)\) 次,推平 \(O(n)\) 次,总计 \(O(2n)\) 能过。
思路
维护栈,第一遍扫过去如果栈空了就把现在这个数加进来,如果栈没空并且栈顶和现在这个位置询问出来是不诚实的人,就将栈顶踢掉,并且跳过现在这个人,否则就把现在这个人加入栈。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+9;
int vis[N],cnt[N];
stack<int>stk;
std::vector<int> solve(int A,int B);
std::vector<int> solve(int A,int B){
vector<int>ans;
while(stk.size())stk.pop();
ans.clear();
if(A<=B) {
for(int i=1;i<=A+B;i++)
ans.push_back(-1);
return ans;
}
for(int i=1;i<=A+B;i++){
if(stk.empty() || query(stk.top()-1,i-1))stk.push(i);
else stk.pop();
}
for(int i=1;i<=A+B;i++)
ans.push_back(query(stk.top()-1,i-1));
return ans;
}
后记
考场上把这个题当交互使,代码可能略显晦涩,可以私信联系。

浙公网安备 33010602011771号