51nod 1821 最优集合 (思维+并查集or栈)

来源:互联网 发布:网络电视怎么看电影 编辑:程序博客网 时间:2024/06/05 20:10

1821 最优集合
基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题
 收藏
 关注
一个集合S的优美值定义为:最大的x,满足对于任意i∈[1,x],都存在一个S的子集S',使得S'中元素之和为i。
给定n个集合,对于每一次询问,指定一个集合S1和一个集合S2,以及一个数k,要求选择一个S2的子集S3(|S3|<=k),使得S1∪S3的优美值最大。
(集合元素可以重复)
Input
第一行一个数n,(n<=1000)接下来n行,每行描述一个集合:第一个数m,表示集合大小,接下来m个数,表示集合中的元素(m<=1000,元素<=10^9)第n+2行一个数T,表示询问次数(T<=10000)接下来T行,每行3个数a,b,k,表示指定第a个集合为S1,第b个集合为S2,k的意义如题(a<=n,b<=n,k<=100,000)
Output
T行,每行一个数,表示对应询问所能达到的最大优美值
Input示例
26 1 2 3 8 15 326 1 1 1 1 1 111 2 3
Output示例
64
﹡    LH (题目提供者)


转自这题相关讨论里 zhenhao  大神题解:

先看下怎么快速求出一个集合的最优值吧!

假设现在得到可以凑出[1,t],那么假设有一个数x加进去:

if x<=t+1,那么这个数是可以插入使得可以凑出[1,t+x];

else能够凑出的部分还是[1,t];

所以将集合的元素排序一次,模拟直到找不到上述过程的x即可求出最优值。

然后现在有一个集合B,可以向A添加k个元素,先看A能够得到最优值是t,然后在B中选一个x满足x<= t+1最大,然后增加A的最优值为t+x,继续在A中得到新的最优值t1,然后继续在B中找到一个x满足x<=t1+1最大,增加A的最优值为t1+x,上述过程做k次即可。

想要得到O(n)的复杂度完成上述过程,我的做法是维护一个栈,将找到的满足x<=t+1的x放入栈中知道不满足,那么取出栈顶元素就是最适合放进A的值。所以总的复杂度是O(T*(n+m))。


大神用的栈,网上许多人用的并查集,因为如果每次都从b的开头从头扫得话,会t,记录之前扫到哪里,还是要倒着扫回去,看哪些没用过,这个栈跟并查集都很好,栈的话,符合条件的都加到栈里,用掉的就直接pop掉就了好了。并查集,一开始每个点都指向自己,如果用到这个点就指向他前一个的祖先,因为并查集是联通的,其实指向前一个就是指向最后一个没用过的数字,如果指向了0说明都用过了。。


总结下:并查集可以直接找出区间最后一个没被用过的元素或者说在区间不断被扩大的情况下可以找出每个元素前面最后一个没被标记的元素,根据这个举一反三也可以维护区间的长度。。


栈的代码:

#include <iostream>#include <algorithm>#include <cstring>#include <cstdio>#include <stack>#include <vector>using namespace std;const int maxn = 1e3 + 10;typedef long long ll;int a[maxn][maxn];inline int read(){    char c = getchar();    int x = 0, f = 1;    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}    while(c >= '0' && c <= '9') {x = x*10+c-'0';c = getchar();}    return x*f;}int main(){    int n, m, t, x, y, k;    scanf("%d", &n);    for(int i = 1; i <= n; i++)    {        a[i][0] = read();        for(int j = 1; j <= a[i][0]; j++)            a[i][j] = read();        sort(a[i]+1, a[i]+a[i][0]+1);    }    t = read();    while(t--)    {        x = read();        y = read();        k = read();        stack<int> s;        int cnt = 0;        int i = 1, j = 1;        while(k--)        {            for(; i <= a[x][0]; i++)            {                if(a[x][i] <= cnt+1)                    cnt += a[x][i];                else                    break;            }            for(; j <= a[y][0]; j++)            {                if(a[y][j] <= cnt+1)                    s.push(a[y][j]);                else                    break;            }            if(s.size())            {                cnt += s.top();                s.pop();            }        }        for(; i <= a[x][0]; i++)        {            if(a[x][i] <= cnt+1)                cnt += a[x][i];        }        printf("%d\n", cnt);    }    return 0;}

并查集代码:

#include <iostream>#include <cstring>#include <cstdio>#include <algorithm>using namespace std;const int maxn= 1e3 + 5;int pre[maxn];int Find(int x){    return pre[x] == x ? x : pre[x] = Find(pre[x]);}int a[maxn][maxn];inline int read(){    char c = getchar();    int x = 0, f = 1;    while(c < '0' || c > '9') {if(x == '-') f = -1; c = getchar();}    while(c >= '0' && c <= '9') {x = x*10+c-'0'; c = getchar();}    return x*f;}int main(){    int n, m, t, x, y, k;    scanf("%d", &n);    for(int i = 1; i <= n; i++)    {        a[i][0] = read();        for(int j = 1; j <= a[i][0]; j++)            a[i][j] = read();        sort(a[i]+1, a[i]+1+a[i][0]);    }    t = read();    while(t--)    {        x = read();        y = read();        k = read();        int cnt = 0;        int i = 1, j = 1;        for(int i = 0; i <= maxn; i++)            pre[i] = i;        while(k--)        {            for(; i <= a[x][0]; i++)            {                if(a[x][i] <= cnt+1)                    cnt += a[x][i];                else                    break;            }            for(; j <= a[y][0]; j++)            {                if(a[y][j] > cnt+1)                    break;            }            j--;            int pos = Find(j);            if(!pos) continue;  //如果一个没有就过掉            cnt += a[y][pos];  //每个元素都指向前面最后一个能用的,所以一开始都是自己本身            pre[j] = Find(pos-1);  //用完指向前面的祖先        }        for(; i <= a[x][0]; i++)            if(a[x][i] <= cnt+1)                cnt += a[x][i];        printf("%d\n", cnt);    }    return 0;}