codeforces 967 D Resource Distribution

题目

One department of some software company has n servers of different specifications. Servers are indexed with consecutive integers from 1 to n. Suppose that the specifications of the j -th server may be expressed with a single integer number cj of artificial resource units.

In order for production to work, it is needed to deploy two services S1 and S2 to process incoming requests using the servers of the department. Processing of incoming requests of service Si takes xi resource units.

The described situation happens in an advanced company, that is why each service may be deployed using not only one server, but several servers simultaneously. If service Si is deployed using ki servers, then the load is divided equally between these servers and each server requires only xi / ki (that may be a fractional number) resource units.

Each server may be left unused at all, or be used for deploying exactly one of the services (but not for two of them simultaneously). The service should not use more resources than the server provides.

Determine if it is possible to deploy both services using the given servers, and if yes, determine which servers should be used for deploying each of the services.

Input
The first line contains three integers n , x1, x2 ( 2n300000 , 1x1,x2109 ) — the number of servers that the department may use, and resource units requirements for each of the services.

The second line contains n space-separated integers c1, c2 , ... , cn ( 1ci109 ) — the number of resource units provided by each of the servers.

Output
If it is impossible to deploy both services using the given servers, print the only word “No” (without the quotes).

Otherwise print the word “Yes” (without the quotes).

In the second line print two integers k1 and k2 ( 1k1,k2n ) — the number of servers used for each of the services.

In the third line print k1 integers, the indices of the servers that will be used for the first service.

In the fourth line print k2 integers, the indices of the servers that will be used for the second service.

No index may appear twice among the indices you print in the last two lines. If there are several possible answers, it is allowed to print any of them.

Examples

Input
6 8 16
3 5 2 9 8 7
Output
Yes
3 2
1 2 6
5 4

Input
4 20 32
21 11 11 12
Output
Yes
1 3
1
2 3 4

Input
4 11 32
5 5 16 16
Output
No

Input
5 12 20
7 8 4 11 9
Output
No

Note
In the first sample test each of the servers 1 , 2 and 6 will will provide 8/3=2.(6) resource units and each of the servers 5 , 4 will provide 16/2=8 resource units.

In the second sample test the first server will provide 20 resource units and each of the remaining servers will provide 32/3=10.(6) resource units.


分析

【题意】
给出 n 个服务器的资源数c1,c2,...,cn和运行 2 个程序需要的资源x1,x2,求一个解决方案

  • 选取 k1 个资源数不小于 x1k1 的服务器使资源总和不小于 x1
  • 选取 k2 个资源数不小于 x2k2 的服务器使资源总和不小于 x2
  • 两次选取的服务器不能有交叉

【分析】

  1. 服务器贪心:连续选择 x1cmin 个服务器
    假设对于x1已经选择了n个服务器,要求每个服务器的资源数都不小于 x1n ,只要最小的资源数不小于这个值就行了,也就是 cminx1ncminnx1 。可以理解为,一个服务器的集合实际上最多提供 cminn 个资源。那么从贪心的角度,其他服务器在大于 cmin 的同时应该尽可能的小,如果把服务器按从大到小排序,就应该从 cmin 向左连续选n-1个。事实上,在从大到小排序的基础上,从一个点开始往左连续选择,那么这个点就是 cmin ,而我们只需要一共选择 x1cmin 个就足够让 x1 运行了。

  2. 如何寻找解:后分配的在左边继续寻找
    假设现在为x1分配服务器,想象所有的服务器按从大到小排成一列,如果从中间的某个地方开始往左选若干个出来,那么势必会将这个序列分割为两端,是不是应该到两边都试一下给x2分配服务器呢?
    list1

cmin 开始向左选择是,大小是递增的,每个服务器都一定会提供 cmin 个资源,所以总的个数是明确的,只需要判断一下左边有没有这个多服务器就能直到有没有解;但是向右选择,服务器越来越小,没有直接的依据应该截取到哪个位置,需要把右边都遍历完才能知道是否有解,复杂度很高,这样做显然不好。

既然向右寻找行不通,那么如果先给 x1 分配服务器了,再给 x2 分配就只能从 x1 的左边继续寻找。为了考虑 x2 在右边的情况,就让 x2 先分配,再让 x1 在x2的左边寻找(向左=低复杂度)。

【算法】
先尝试先分配 x1 ,再分配 x2 ,遍历所有位置 i 作为x1分配的起点,分配的终点就是 ix1cmin ,再在分配终点继续向左为 x2 分配;
再尝试先分配 x2 ,再分配 x1 ,原理和上述一样。

我自己感觉,连续的分配是最贪心解,也是最优的解,如果有不是连续的分配,也一定可以通过交换变成连续的分配,因为分配的所有服务器,只有最右边的那一个在决定整体的大小,所以其实在它的左边选哪些服务器并不影响最后的大小,只要数量是 x1cmin 就行了。至于具体怎么证明就不会了。。。

代码

#include<stdio.h>
#include<algorithm>
using std::sort;

/*
 * 给c1, c2, ..., cn和x1, x2
 * 选取k1个不小于x1/k1的组成x1,再用剩下的选k2个不小于x2/k2的组成x2
 * 只需给出一个解
 * 
 */

#define maxn 300010
#define max(a,b) ((a)>(b)?(a):(b))
#define updiv(a,b) ((a)/(b)+(((a)%(b))!=0))
struct thing {
    int v;
    int id;
}c[maxn];   

bool cmp(thing t1,thing t2) {
        return t1.v>t2.v;
}
int n, a, b;
int k1, k2;
int main() {
    scanf("%d%d%d", &n, &a, &b);
    for(int i=1;i<= n;++i)
        scanf("%d", &c[i].v), c[i].id = i;
    sort(c + 1, c + n + 1,cmp);
    //尝试先分配a,再分配b
    for (int i = 1; i <= n; i++) {//只前i个能否构成解
        //以服务器i为下限组成a需要前k1个
        k1 = updiv(a, c[i].v);
        if (k1 >= i)continue;
        //把[i-k1+1,i]分配给a
        //以服务器i-k1为下限组成b需要前k2个
        k2 = updiv(b, c[i - k1].v);
        if (k2 > i - k1)continue;
        //把[i-k1-k2+1,i-k1]分配给b

        //分配成功
        printf("Yes\n%d %d\n", k1, k2);
        for (int t = i - k1 + 1; t <= i; ++t)printf("%d%c", c[t].id, t == i ? '\n' : ' ');
        for (int t = i-k1-k2+1; t <=i-k1; ++t)printf("%d%c", c[t].id, t == i-k1 ? '\n' : ' ');
        return 0;
    }

    //尝试先分配b,再分配a
    for (int i = 1; i <= n; i++) {//只前i个能否构成解
        //以服务器i为下限组成b需要前k2个
        k2 = updiv(b, c[i].v);
        if (k2 >= i)continue;
        //把[i-k2+1,i]分配给b

        //以服务器i-k2为下限组成a需要前k1个
        k1 = updiv(a, c[i - k2].v);
        if (k1 > i - k2)continue;
        //把[i-k2-k1+1,i-k2]分配给a

        //分配成功
        printf("Yes\n%d %d\n", k1, k2);
        for (int t = i-k2 - k1 + 1; t <= i-k2; ++t)printf("%d%c", c[t].id, t == i-k2 ? '\n' : ' ');
        for (int t = i-k2+1; t <= i; ++t)printf("%d%c", c[t].id, t == i ? '\n' : ' ');
        return 0;
    }
    printf("No");
    return 0;
}

【总结】
遇到直接做不好分析的问题,不妨先着眼于解空间的构成,如果是贪心问题,就更应该去思考最优解是怎样的存在,如何高效的构造出来才是问题的关键,如果做不到高效的构造,又怎么贪心呢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值