LG 题解 P7321 「PMOI-4」猜排列
- 写在前面
- Description
- Solution
- Subtask1
- Subtask2
- Subtask3,4,5
- Subtask6
- Subtask7
- 代码实现
- Code
写在前面
感谢 @zimujunqwq 的思路。
Description
题目描述
不知如何简化题意
Solution
Subtask1
考虑用询问 \(2\) 把 \(3, 4\) 的元素位置确定,那么剩下的 \(1,2\) 的两个位置也能确定。
注意我们暂时还不能区分他们。
此时利用询问 \(1\),询问 \(3,4\) 两个位置对 \(1,2\) 两个位置取模的结果。
他们的结果是不同的,所以可以确定出 \(1,2,3,4\) 对应的位置了。
期望得分 \(10pts\)。
Subtask2
通过观察数据范围发现 \(m_2^2 = m_3\)。
所以注定这是一个 \(O(n^2)\) 算法
我们可以利用 \(2\) 操作询问 \(n\) 次。把 \(p\) 从 \(n\) 不断下调,即每次询问 \(p\) 都比上一次小 \(1\)。
这样每次询问我们都会多知道一个元素的位置,而这个元素的值也是确定的。
期望得分 \(10 pts\)
Subtask3,4,5
反正我没看出 Subtask3,4 有什么单独的做法,有卡到这一档的可以单独和我说一声。
注意到后面的数据 \(n = m_1\),所以我们可能每次询问 \(1\) 都要确定一个位置。
现在假设我们知道了 \(\left \lceil \frac{n}{2} \right \rceil\) 的位置,那么不难发现, \(\left \lceil \frac{n}{2} \right \rceil + 1 \sim n\) 这些数对 \(\left \lceil \frac{n}{2} \right \rceil\) 取模的值是互不相同的。这就启发我们可以先确定出 \(\left \lceil \frac{n}{2} \right \rceil\) 的位置,然后通过几次询问确定他后面的数的位置。注意模数为 \(0\) 时要特判一下。
这样的话,问题的规模就会缩小一半。
我们就可以继续处理剩下一半的情况。
如何确定 \(\left \lceil \frac{n}{2} \right \rceil\) 的位置?
可以询问两次询问 \(2\) 。第一次求出 \(>\left \lceil \frac{n}{2} \right \rceil\) 的所有位置,第二次求出 \(\ge \left \lceil \frac{n}{2} \right \rceil\) 的所有位置,那么新增加的位置就是 \(\left \lceil \frac{n}{2} \right \rceil\)。
我们可以发现,这样递归处理需要 \(\log n\) 次,并且数据范围是 \(2 \log n \le m_2\)。
期望得分 \(50pts\)
Subtask6
观察数据发现 \(2^{17} \ge 5 \times 10^4\)。
那就是说只让我们进行 \(\log n\) 次询问。
我们考虑通过询问 \(1\) 把 \(\left \lceil \frac{n}{2} \right \rceil\) 的位置求出来。我们想知道他的位置可以通过知道 \(\left \lceil \frac{n}{4} \right \rceil\) 的位置求出来。然后我们不断这样递归下去,发现我们只要知道了 \(1\) 的位置,并且知道了各个段的位置,就可以回溯到整个序列。而 \(1\) 的位置会在最后我们通过询问 \(2\) 询问 \(p=1,2\) 的时候求出。
这样的话,我们就可以在 \(\log n\) 次询问 \(2\) 内求出所有位置。
期望得分 \(25pts\)。
Subtask7
这个之后我们发现 \(2^{15} \le 5 \times 10^4\) 。
怎么压缩这两次询问?
通过自己手模每次询问的位置发现最后几个询问是 \(1,2,3,5\),我们考虑把前两个询问去掉。
最后询问 \(3\) 的时候,我们得到的两个位置一定是 \(3,4\),未知的两个位置是 \(1,2\)。
这是什么?Subtask1 啊!
然后此题完结。
需要注意的一点是,Subtask3,4 的数据下,最后几次询问的位置是 \(1,2,3,4,6\),也就是说 \(3,4\) 没有被划分到一段内。这个情况更好处理 \(1,2\) 因为我们知道了 \(3\) 的位置。
我们要爱惜自己的询问次数,因为 \(n = m_1\),不能浪费,所以确定出前几个数后,再求其他数的时候要从已经确定的数的下一个开始求。
代码实现
我用 \(col\) 按照第几次询问标记了每个段的颜色,然后利用一个 \(c\) 数组将颜色从大到小排序,这样每段颜色都被放在了一起,颜色相同的段在值域上也是连续的,就可以 \(O(n)\) 的求出整个序列了。
实现细节参考代码吧。
Code
/*
Work by: Suzt_ilymics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include
#include
#include
#include
#include
#include
#define LL long long
#define orz cout<<"lkp AK IOI!"<= 1; --i) {
printf("? %d ", n);
for(int j = 1; j <= n; ++j) printf("%d ", j);
printf("%d\n", i);
fflush(stdout);
int k = read();
for(int j = 1, x; j <= k; ++j) {
x = read();
if(vis[x]) continue;
vis[x] = true;
ans[x] = i;
}
}
printf("A");
for(int i = 1; i <= n; ++i) {
printf(" %d", ans[i]);
}
puts("");
fflush(stdout);
}
}
bool cmp(int x, int y) { return vis[x] > vis[y]; }
void Divide(int l, int r, int col) {
int mid = (r + 1) / 2 + 1;
if(mid == 2) {
for(int i = 1; i <= n; ++i) if(!vis[i]) vis[i] = col;
sort(c + 1, c + n + 1, cmp);
return ;
}
sc = 0;
for(int i = 1; i <= n; ++i) {
if(!vis[i]) stc[++sc] = i;
}
printf("? %d ", sc);
for(int i = 1; i <= sc; ++i) printf("%d ", stc[i]);
printf("%d\n", mid);
fflush(stdout);
int k = read();
for(int i = 1, x; i <= k; ++i) {
x = read();
vis[x] = col;
}
Divide(l, mid - 1, col + 1);
}
int main()
{
n = read(), m1 = read(), m2 = read(), m3 = read();
if(n == 4) {
Subtask1::Solve();
} else if(n == 500){
Subtask2::Solve();
} else {
for(int i = 1; i <= n; ++i) c[i] = i;
Divide(1, n, 1);
// for(int i = 1; i <= n; ++i) cout<