[APIO2017]考拉的游戏,构造与思维题

本文详细解析了一道竞赛题目,涉及四个子任务,从简单到复杂,主要讨论如何利用二分法和策略调整来判断球的放置位置,以确保在给定限制下做出最优选择。核心在于理解球数对选择策略的影响,以及如何通过计算边界值来缩小搜索范围。
摘要由CSDN通过智能技术生成

正题

这题还挺好玩的,花了6h左右吧。ztO emofunc Orz
只有第三个subtask比其他人的更严格,其他的大致相同,最后一个subtask可以做到 O ( n 2 l o g 2 w ) O(n^2log_2 w) O(n2log2w)

Subtask1

十分的简单,只需要随便给一个点放一个就行,不选的那个就是最小的。

Subtask2

首先考虑第一次给每一个点都放一个,这样可以选出来 [ 51 , 100 ] [51,100] [51,100]
然后再给这50个放两个,考虑什么样的点会被选到,假设先选了 [ 1 , 50 ] [1,50] [1,50],剩下50个球选 [ 85 , 100 ] [85,100] [85,100],然后开始调整决策,比较 1 1 1 84 84 84(还剩下两个球,只需要多增添一个),然后比较 2 + 3 + 4 2+3+4 2+3+4 83 83 83,…,可以写一个程序帮助自己实现。就会发现第二次会选出来 [ 76 , 100 ] [76,100] [76,100],第三次给这 25 25 25个球放 4 4 4个,选出来 [ 92 , 100 ] [92,100] [92,100],第四次给这 9 9 9个球放 11 11 11个,选出来 [ 100 , 100 ] [100,100] [100,100]

Subtask3

我认为的重头戏来了。
这个 S u b t a s k Subtask Subtask因为不严谨所以被好多人说错了二分界限,虽然可以用正确的次数得到正确的答案,但是没有深究其中的道理。
可以想到,如果要比较两个位置的值,就分别给这两个位置都放上 w w w个球,当有一方选而另一方不选的时候,答案就出来了。
我们先来确定当有 w w w个球时,两个位置都选的话,值满足什么样的条件,设两个值分别为 a , b , a < b a,b,a<b a,b,a<b
w = 1 w=1 w=1时,由于当前手中还有两个球,可以先选一个 b b b a a a至少要满足 > 1 + 2 = 3 \gt 1+2=3 >1+2=3时, a a a才会被选。(此处虽然说的不严谨,仅仅提供了一种方式去猜 a a a的下界,但可以发现,实际上只用分两种情况来讨论,即 a < = 3 a<=3 a<=3或者 a > 3 a>3 a>3,可以发现,在 a < = 3 a<=3 a<=3时,无论怎么取,取到的两个球都 ≥ a \geq a a,在 a > 3 a>3 a>3时,换掉的最小的两个球恰好是 1 + 2 = 3 1+2=3 1+2=3,所以 a > 3 a>3 a>3这个条件是充分必要的,下面同理。)
w = 2 w=2 w=2时,由于当前手中还有两个球,可以先用 1 1 1选一个 b b b a a a至少要满足 > 2 + 3 + 4 = 9 \gt 2+3+4=9 >2+3+4=9时, a a a才会被选。
w = 3 w=3 w=3时,由于当前手中还有两个球,可以先用 1 , 2 1,2 1,2选一个 b b b a a a至少要满足 > 3 + 4 + 5 + 6 = 18 \gt 3+4+5+6=18 >3+4+5+6=18时, a a a才会被选。
以此类推,我们可以很轻易算出如果两个球都取的话满足的下界条件。

球数下界 x x x(即 a , b > x a,b>x a,b>x)
13
29
318
430
545
663
784
8108

我们再来讨论一下如果两者都不选的情况下, a , b a,b a,b满足的上界大小。
这里提供一种计算上界的方法,即只用考虑 a = 1 , b = x a=1,b=x a=1,b=x的情况,若 a ≠ 1 , b = x a\neq 1,b=x a=1,b=x,这样会使得替换数的和变小,更可能替换 b b b,而我们需要找到的是一个保证的上界,所以我们只用考虑上界最松的限制,即当 a = 1 , b = x a=1,b=x a=1,b=x时。
w = 1 w=1 w=1时,因为手中有两个球,所以必选一个,即 b < = 0 b<=0 b<=0
w = 2 w=2 w=2时,因为手中有两个球,只需要另外替换出一个就可以选,即 b < = 2 b<=2 b<=2
w = 3 w=3 w=3时,因为手中有两个球,另外替换出两个就可以选,即 b < = 2 + 3 = 5 b<=2+3=5 b<=2+3=5
以此类推,我们可以很轻易算出如果两个球都取的话满足的上界条件。

球数上界 x 1 x_1 x1(即 a , b < = x 1 a,b<=x_1 a,b<=x1)下界x_2(即 a , b > x 2 a,b>x_2 a,b>x2)
103
229
3518
4930
51445
62063
72784
835108

对于这个表格我们很轻易就可以看出,在 w = i w=i w=i时两个都选,那么在 w = i + 1 w=i+1 w=i+1时就不可能两个都不选,而当 w = 8 w=8 w=8时只可能不选或者一个选一个不选,在 w = 1 w=1 w=1时,只可能都选或者一个选一个不选,所以就一定可以在 w = [ 1 , 8 ] w=[1,8] w=[1,8]中二分出一个答案。这样需要二分 4 4 4次,并不能满足条件。
一个简单的缩减二分范围的方法就是直接把 7 7 7去掉,因为当 w = 6 w=6 w=6时两个都选的话,在 w = 8 w=8 w=8时就不可能两个都不选,因为 63 > 35 63>35 63>35,那么就做完了。
实际上,我们可以将 w = x w=x w=x可以区分的答案写成 x 1 < ( a   o r   b ) < = x 2 x_1<(a\ or\ b)<=x_2 x1<(a or b)<=x2
所以我们只需要找出若干个球使他们的并集包括 [ 1 , 100 ] [1,100] [1,100]就可以了。
但这还没完,因为可以通过尝试发现 w ∈ [ 1 , 3 , 5 , 8 ] w\in [1,3,5,8] w[1,3,5,8]时,会通过此题,也许你会问到 a = 4 , b = 5 a=4,b=5 a=4,b=5的情况怎么办?,那是因为下界在 a ≠ 1 , b = x a\neq 1,b=x a=1,b=x时,是会变窄的,一个球数管理的答案也就变多了,想要找出所有的方案吗?自行提交 2 8 2^8 28次就可以找出来了,也许下一个最优解就是你。

Subtask4

十分简单,考虑怎么用 1 1 1次比较两个位置,在每一个位置上放上 100 100 100个球就可以了,这样他一定会选一个。

Subtask5

由于只给了 100 100 100次,考虑分治,每次想办法将一个值域区间用 1 1 1次的代价 [ l , r ] [l,r] [l,r]分成两部分。
我们考虑在值为 [ l , r ] [l,r] [l,r]的位置上放上 k k k个球,看一下是否选一部分不选一部分即可。
怎么计算 k k k
显然满足二分性质,当 k k k越小的时候,越有可能取, k k k越大的时候,越不可能取。
k k k可以使得区间全部被选时,增加答案,全部不被选时减小答案,否则找到合法的 k k k
这样我们就可以用 n log ⁡ 2 w n \log_2 w nlog2w次找出答案,还是不行。
发现区间 [ l , r ] [l,r] [l,r]内放 k k k个球,其他不放,取最大代价这种事情可以自己干,只需要枚举区间内选几个,其他的贪心选就可以了,时间复杂度 O ( n ) O(n) O(n)
所以总的尝试次数就变成 n − 1 n-1 n1了,时间复杂度就变成 O ( n 2 log ⁡ 2 w ) O(n^2\log_2 w) O(n2log2w)了。
由于 n n n 100 100 100,所以博主直接写了暴力枚举。

#include<bits/stdc++.h>
using namespace std;
void playRound(int*,int*);

const int N=110;
int a[N],b[N],n,w;

int minValue(int n,int w){
	a[0]=1;
	playRound(a,b);
	for(int i=0;i<n;i++) if(!b[i]) return i;
}

int maxValue(int n,int w){
	for(int i=0;i<n;i++) a[i]=1;
	for(int t=1;t<=4;t++){
		playRound(a,b);
		int tot=0;
		for(int i=0;i<n;i++) if(b[i]>a[i] && a[i])
			tot++;
		for(int i=0;i<n;i++) 
			if(b[i]>a[i] && a[i]) a[i]=w/tot;
			else a[i]=0;
        if(tot==1) break;
	}
	for(int i=0;i<n;i++) if(a[i]) return i;
}

int greaterValue(int n,int w){
    int l=1,r=4;
    int op[10]={0,1,3,5,8};
	while(l<=r){
		int mid=(l+r)/2;
		a[0]=a[1]=op[mid];
		playRound(a,b);
		if((b[0]>a[0]) ^ (b[1]>a[1])) return (b[0]>a[0])?0:1;
		else if(b[0]>a[0]) l=mid+1;
		else r=mid-1;
	}
}

bool check(int x,int l,int r){
	int mmax=l*(l-1)/2,pos=0,tot=w-(n-r)-x-1,ans=0;
	for(int t=r;t>=l && tot>=0;t--,tot-=x+1){
		ans+=t;
		if(mmax<ans+(l-1+l-tot)*tot/2) mmax=ans+(l-1+l-tot)*tot/2,pos=t;
	}
	return pos!=0 && pos!=l;
}

int get_value(int l,int r){
	for(int i=1;i<=w/(r-l+1);i++)
		if(check(i,l,r)) return i;
}

void solve(int l,int r,vector<int>&op,int*num){
	if(l==r){
		num[op[0]]=l;
		return ;
	}
	int k=get_value(l,r);
	vector<int> L,R;
	memset(a,0,sizeof(a));
	for(int i=0;i<op.size();i++) a[op[i]]=k;
	playRound(a,b);
	for(int i=0;i<op.size();i++){
		if(b[op[i]]>k) R.push_back(op[i]);
		else L.push_back(op[i]);
	}
	solve(l,l+L.size()-1,L,num);
	solve(l+L.size(),r,R,num);
}

void allValues(int N,int W,int*c){
	vector<int> V;n=N;w=W;
	for(int i=0;i<n;i++) V.push_back(i);
	solve(1,n,V,c);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值