[WC2022-DMY]疯狂优化

220 篇文章 2 订阅
27 篇文章 0 订阅

题目

题目描述
考虑这样一个游戏: 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 xy 是否成立。 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 xi 是否成立,并且 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 n2×103

思路

首先需要巧妙的问题转化。考虑何时能确定 x x x,那就是说除了 x x x 之外的 v v v 都不可能作为答案。即使考虑撒谎。何时呢?那就是被 “非答案区间” 覆盖了两次。举例:若询问 x ⩾ y x\geqslant y xy 得到回答 “是”,则 [ 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值