题目
题目描述
考虑这样一个游戏:
A
l
i
c
e
\rm Alice
Alice 事先选定整数
x
∈
[
1
,
n
]
x\in[1,n]
x∈[1,n] 。此后
B
o
b
\rm Bob
Bob 可选择整数
y
∈
[
1
,
n
]
y\in[1,n]
y∈[1,n],询问
x
⩾
y
x\geqslant y
x⩾y 是否成立。
A
l
i
c
e
\rm Alice
Alice 会做出回答。但是
A
l
i
c
e
\rm Alice
Alice 有一次撒谎的机会。
现在,模拟多次不同游戏。第 i i i 次,假设 B o b \rm Bob Bob 最初问 x ⩾ i x\geqslant i x⩾i 是否成立,并且 A l i c e \rm Alice Alice 回答 “是”,若二人都持最优策略, B o b \rm Bob Bob 还需多少次询问来确定 x x x 。注意 A l i c e \rm Alice Alice 的这次回答可能是说了谎的。
数据范围与提示
n
⩽
2
×
1
0
3
n\leqslant 2\times 10^3
n⩽2×103 。
思路
首先需要巧妙的问题转化。考虑何时能确定 x x x,那就是说除了 x x x 之外的 v v v 都不可能作为答案。即使考虑撒谎。何时呢?那就是被 “非答案区间” 覆盖了两次。举例:若询问 x ⩾ y x\geqslant y x⩾y 得到回答 “是”,则 [ 1 , y ) [1,y) [1,y) 就是 “非答案区间”。被多次覆盖则无法通过撒谎弥补。
上面的想法很棒的是,不在过程中确定 撒谎的回答,而是最后 统筹安排。这就跟 “将询问离线” 一样有用。
那么,被覆盖至少两次的部分可以忽略,剩余部分连接起来,必然是若干 1 1 1 和若干 0 0 0 和若干 1 1 1 。这里数字表示被覆盖次数。直接用这个状态 d p \tt dp dp 则是 O ( n 3 ) \mathcal O(n^3) O(n3) 的状态, O ( n ) \mathcal O(n) O(n) 的转移。
从原问题中,我们发现 答案很小,不超过 2 log n 2\log n 2logn 。因为每个 y y y 问两次,就可以辨别谎言。又发现上面的状态在每一维上都是单增的,所以记 f ( a , b , w ) f(a,b,w) f(a,b,w) 为,最后一段 1 1 1 在答案不超过 w w w 时能有多长。
转移如何优化?利用 决策点单调性。当 a , w a,w a,w 固定时,决策点 x x x 随着 b b b 增大而减小。这是不难证明的。这样一来总复杂度就是 O ( n 2 log n ) \mathcal O(n^2\log n) O(n2logn) 了。
代码
对于三种情况,分别讨论一下。
#include <cstdio> // megalomaniac JZM yydJUNK!!!
#include <iostream> // Almighty XJX yyds!!!
#include <algorithm> // oracle: ZXY yydBUS!!!
#include <cstring> // DDG yyd Black Bridge!!!
#include <cctype> // decent XYX yydLONELY!!!
typedef long long llong;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
for(; isdigit(c); c=getchar()) a = a*10+(c^48);
return a*f;
}
const int MAXN = 2001, LOGN = 17;
int f[LOGN][MAXN][MAXN], pos[LOGN-1][MAXN];
const int INF = 0x3FFFFFFF;
int main(){
int n = readint();
memset(f,-1,sizeof(f)); // will it work?
f[0][0][0] = 1, f[0][1][0] = f[0][1][1] = 0;
rep0(i,0,LOGN-1) rep(a,0,n) pos[i][a] = INF;
rep0(w,0,LOGN-1) rep(level,0,n)
for(int a=0,b=level; ~b; ++a,--b){
int &x = pos[w][a], &c = f[w+1][level][a];
if(f[w][level][a] >= 0){ // in the third part
x = level+f[w][level][a];
c = f[w][0][0]+x-level-b; continue;
}
if(x >= a){ // middle part
x = std::min(x,level); // not to exceed
while(x >= a && level-x > f[w][x][a]) -- x;
}
if(x >= a) c = f[w][b][x-a]; // random access :(
else{ // worst part
x = f[w][0][0]-b;
if(x >= 0) c = f[w][a+b-x][a-x];
}
}
for(int i=0,ans=0; i!=n; ++i,ans=0){
while(f[ans][n][i] < 0) ++ ans;
printf("%d ",ans);
}
putchar('\n');
return 0;
}