zoukankan      html  css  js  c++  java
  • @loj


    @description@

    V 君、I 君和 Y 君是好朋友。

    I 君最近开了一家商店,商店里准备了 N 种物品(编号为 0~N-1 中的整数),每种物品均有无限个可供出售,每种物品的单价是 0 或者 1。

    V 君想知道每个物品的价格,他已经通过某种超自然力量知道,这 N 个物品里,价格是 1 的物品恰好有奇数/偶数个,且至少存在一个物品的价格是 1

    然而, V 君并不想自己去问 I 君,他选择了这样一种方法:他准备了 +∞ 的钱给 Y 君。然后让 Y 君帮他跑腿:每一次,他会给 Y 君指定两个非空物品集合 (S, T)(同一个集合内的物品必须两两不同,即每种物品在每个集合类最多包含一个),Y 君会跑到商店,分别买下这两个集合的物品,把他们送回来,并告诉 V 君哪个集合的物品价格之和更高。但是,当两集合价格之和相等的时候,Y 君会按照 I 君的指示来回答 V 君。

    带着很多物品跑腿是一个很累的事情,因此,我们定义一次跑腿的体力消耗是 S + T。其中,S 表示集合 (S) 包含的物品个数。

    你的任务是:写一个程序,帮助 V 君决定如何合理地让 Y 君跑腿,从而推算出每种物品的价值。Y 君的体力有限,你当然不能让他过于劳 累,也即,你不能让他的总体力消耗超过某个预设的阈值 M。

    实现细节
    你不需要,也不应该实现主函数,你只需要实现下列函数:

    find_price(task_id, N, K, ans)
    其中 task_id 表示子任务编号(见限制与约定)。N 表示物品个数,K 的意义为:
    若 K=0,表示有偶数个物品价值为 1;
    若 K=1,表示有奇数个物品价值为 1。
    你需要将计算出的物品价格放在数组 ans[] 中,其中 ans[i] 表示编号为 i 的物品的价格。

    你可以通过调用如下函数来向交互库发出询问:

    query(S, nS, T, nT)
    这里 nS=S,nT=T, 数组 S [0…(nS − 1)] 和数组 T [0…(nT − 1)] 分别描述两个集合,你需要保证:
    nS, nT>0;
    ∀0≤i<nS,0≤S[i]<N;
    ∀0≤i<nT,0≤T[i]<N;
    调用此函数一次的时间复杂度为 Θ(nS + nT)。它的返回值为 0 或 1,返回值的意义为:
    若集合 S 的物品价格和更大,返回 0;
    若集合 T 的物品价格和更大,返回 1;
    否则,按照某种未知规则返回 0 或 1。
    如题面所述,我们定义这样一次调用的代价为 nS+ nT。

    评测时,交互库可能会调用 find_price 多次(不超过 10 次),每次调用代表一次新的猜价格游戏,所有的物品的价格都会被重新设定。

    子任务
    我们令代价之和的上界为 M,记答案数组为 ans[]:
    子任务 1:N≤5,M=100;
    子任务 2:N≤10^3,M=10^6;
    子任务 3:N≤10^5,M=100,保证 ∀i<j<k,若 ans[i]=ans[k] 则必有 ans[j]=ans[i]。
    子任务 4:N≤10^4,M=2×10^5;
    子任务 5:N≤5×10^4,M=350100;
    子任务 6:N≤10^5,M=500100。

    提示
    I 君可能并不愿意让 V 君知道每件物品的价格,在物品价格相等时,他会按照他自己的某种方式来回答问题。

    @solution@

    做交互题的第一步:首先应该先观察子任务分别的数据范围
    注意到一点:子任务 3 是无论任何都无法与其他子任务合并的。
    我们就从子任务 3 下手。

    注意到子任务 3 要么是 00...011...1 的形式,要么是 11...100...0 的形式。
    不难想到去找这个分界点。而询问次数如此小(甚至不足 N),排列形式如此具有单调性,不难想到或许可以二分。

    做这样的题还有一个原则:尽量简化问题。对应到这道题就是尽量少塞数在 S 与 T 中
    比如,如果令 nS = nT = 1,则可以得到 S 与 T 中的元素哪个大哪个小(但不能得到严格大于还是严格小于)。
    我们可以通过一次 nS = nT = 1 的操作,比较序列开头与末尾,将子任务 3 统一转为 00...011...1 的形式。

    注意到题目中提到序列中必定有一个 1,故我们的序列中最后一个元素一定是 1。
    此时我们发现如果继续 nS = nT = 1 的操作,我们可能无法得到任何有用信息。我们就稍微扩大一点:令 nS = 2, nT = 1。
    我们取相邻的两个数 a, b,则 a <= b。如果 a + b <= 1,则有 a 以及 a 之前的都为 0;否则如果 a + b >= 1,则有 b 以及 b 之后的都为 1。
    因此我们就不断二分,取相邻的两个数 a, b 与最后那个 1 比较。
    注意二分到最后可能剩一个无法判断,需要通过奇偶性来搞。

    解决完子任务 3,我们再来看看其他子任务。
    二分与什么最相配?排序。我们可以通过 nS = nT 的操作将这个序列 sort 一遍,再进行二分。
    这样理论上能过前 4 个子任务,但实测好像第 4 个过不了。

    看子任务 5,M = 7N + 100。后面那个 100 肯定是用来二分的,我们只需要考虑怎么能够在 7N 的次数内进行排序。
    当 nS = nT = 1 时,是基于比较的排序,复杂度下限是 O(nlogn)。我们再次使用 nS = 2, nT = 1。
    上面二分时使用的结论这里还可以用:当 a <= b 时,如果 a + b <= 1,则有 a 为 0;否则如果 a + b >= 1,则有 b 为 1。
    因此我们先花 2*N 找到一个 1(找最大值),然后每次花 2 得到 a <= b 还是 a >= b,再花 3 得到 a + b 与 1 的关系。
    这样就可以花 5 次确定 1 个数是 0 还是 1,最后剩下单独一个数无法判断,直接塞到 0 与 1 之间,然后二分。

    看子任务 6,M = 5N + 100。
    注意到我们子任务 5 中几乎就可以确定整个序列了,二分的作用微乎其微。
    而子任务中 7N = 5N + 2N,2N 是找 1 的复杂度。我们是否可以省掉找 1 的过程?或者说,我们可以动态去找 1,边找 1 边排序?
    如果 a + b <= c 且 a <= b,不管 c 是不是 1,一定有 a = 0。
    如果 a + b >= c 且 a <= b,不管 c 是不是 1,一定有 b >= c。b 更有可能成为 1,因此接下来的比较中不等式右边就放 b。
    由于 c 是之前的最大值,所以最终排好序后 c 一定可以在 b 的前一个(也就是说 c,b 相邻)。我们就确定了 c 与 b 的相对位置。

    这样下来,我们得到了若干 = 0 的,和一条有序的链(互相之间确定了相对位置的数构成的链),和单独剩一个无法判断。
    我们对这条链二分,将链分为若干 = 0 的,若干 = 1 的,一个无法判断的。
    剩下两个无法判断的再根据奇偶性以及几次比较就可以判断出来了。

    @accepted code@

    #include<cstdio>
    #include "shop.h"
    #include<vector>
    #include<algorithm>
    using namespace std; 
    const int MAXN = 100000;
    int num[MAXN + 5];
    int S[MAXN + 5], T[MAXN + 5];
    void solve(int N, int K, int ans[]) {
    	ans[num[N - 1]] = 1;
    	int L = 0, R = N - 1;
    	while( L + 1 < R ) {
    		int mid = (L + R + 1) >> 1;
    		S[0] = num[mid], S[1] = num[mid - 1], T[0] = num[N - 1];
    		if( !query(S, 2, T, 1) ) {
    			for(int i=mid;i<R;i++)
    				ans[num[i]] = 1;
    			R = mid;
    		}
    		else {
    			for(int i=L;i<mid;i++)
    				ans[num[i]] = 0;
    			L = mid;
    		}
    	}
    	if( L != R ) {
    		if( (N - R) % 2 == K )
    			ans[num[L]] = 0;
    		else ans[num[L]] = 1;
    	}
    }
    bool cmp(int a, int b) {
    	S[0] = a, T[0] = b;
    	return query(S, 1, T, 1);
    }
    vector<int>v;
    void find_price(int task_id, int N, int K, int ans[]) {
    	if( task_id == 3 ) {
    		for(int i=0;i<N;i++)
    			num[i] = i;
    		S[0] = 0, T[0] = N - 1;
    		if( !query(S, 1, T, 1) )
    			reverse(num, num + N);
    		solve(N, K, ans);
    	}
    	else if( task_id <= 3 ) {
    		for(int i=0;i<N;i++)
    			num[i] = i;
    		sort(num, num + N, cmp);
    		solve(N, K, ans);
    	}
    	else if( task_id <= 5 ) {
    		int lp = 0, rp = N - 1, mx = 0;
    		for(int i=0;i<N;i++)
    			if( cmp(mx, i) ) mx = i;
    		num[rp--] = mx;
    		int p, q;
    		for(p=0,q=N-1;p<q;) {
    			if( p == mx ) p++;
    			else if( q == mx ) q--;
    			else {
    				S[0] = p, S[1] = q, T[0] = mx;
    				if( query(S, 2, T, 1) ) {
    					if( cmp(p, q) )
    						num[lp++] = p++;
    					else num[lp++] = q--;
    				}
    				else {
    					if( cmp(p, q) )
    						num[rp--] = q--;
    					else num[rp--] = p++;
    				}
    			}
    		}
    		num[lp++] = p;
    		solve(N, K, ans);
    	}
    	else {
    		v.clear();
    		int p, q, lp = 0, mx = N - 1; v.push_back(N - 1);
    		for(p=0,q=N-2;p<q;) {
    			if( p == mx ) p++;
    			else if( q == mx ) q--;
    			else {
    				S[0] = p, S[1] = q, T[0] = mx;
    				if( query(S, 2, T, 1) ) {
    					if( cmp(p, q) )
    						num[lp++] = p++;
    					else num[lp++] = q--;
    				}
    				else {
    					if( cmp(p, q) )
    						v.push_back(q), mx = q--;
    					else v.push_back(p), mx = p++;
    				}
    			}
    		}
    		int rp = N - 1;
    		for(int i=v.size()-1;i>=0;i--)
    			num[rp--] = v[i];
    		ans[num[N - 1]] = 1;
    		int L = rp + 1, R = N - 1;
    		while( L + 1 < R ) {
    			int mid = (L + R + 1) >> 1;
    			S[0] = num[mid], S[1] = num[mid - 1], T[0] = num[N - 1];
    			if( !query(S, 2, T, 1) ) {
    				for(int i=mid;i<R;i++)
    					ans[num[i]] = 1;
    				R = mid;
    			}
    			else {
    				for(int i=L;i<mid;i++)
    					ans[num[i]] = 0;
    				L = mid;
    			}
    		}// rp + 1 ~ L - 1 : 0
    		// R ~ N - 1 : 1
    		if( L != R ) {
    			if( (N - R) % 2 == K ) {
    				S[0] = num[L], S[1] = p, T[0] = num[N - 1];
    				if( query(S, 2, T, 1) ) // S <= T
    					ans[num[L]] = ans[p] = 0;
    				else ans[num[L]] = ans[p] = 1;
    			}
    			else {
    				S[0] = num[L], T[0] = p;
    				if( query(S, 1, T, 1) ) // S <= T
    					ans[num[L]] = 0, ans[p] = 1;
    				else ans[num[L]] = 1, ans[p] = 0;
    			}
    		}
    		else {
    			if( (N - R) % 2 == K )
    				ans[p] = 0;
    			else ans[p] = 1;
    		}
    	}
    }
    

    @details@

    考场上想到二分,没想到排序,错失金牌,GG。

    NOI 之前想写一下这道题练练手,结果没写出来,注定了我 NOI 只能打银的结局,GG。

    现在总算是根据当时讲题的记忆勉强写了出来。。。

  • 相关阅读:
    【性能测试】:loadrunner直压MYSQL数据库的脚本开发
    【性能测试】:oracle数据库的监控方式
    【性能测试】:监控Mysql数据库方式
    【性能测试】:JVM内存监控策略的方法,以及监控结果说明
    【性能测试】:记录一次性能测试项目过程
    【性能测试】:解决loadrunner执行脚本启动慢的问题
    【性能测试】:关于清除脚本中的垃圾文件的方法
    【性能测试】:关于Sockets协议的脚本的开发
    【性能测试】:对WebSphere中间件的监控方式
    【性能压测】:MQ队列异步处理机制导致的系统无法接受请求的问题
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11712244.html
Copyright © 2011-2022 走看看