《挑战程序设计竞赛》3.2.4 常用技巧-折半枚举 POJ2785 3977 2549

POJ2785

http://poj.org/problem?id=2785

题意

输入n,表示a b c d 四个集合都有n个元素。之后每行输入4个集合中的一个元素。求这四个集合每个集合中拿出一个数相加等于0的组数。

思路

如果直接搜,复杂度为O(N^3),时间不满足要求。
折半搜索比较适合,把4个数字分成两份,分别两两求和,得到两个长度n*n的一维数组,排序后比较进行匹配即可。
另外这个题还可以用hash,具体参考:POJ2785 4 Values whose Sum is 0(哈希)

代码

Source Code

Problem: 2785       User: liangrx06
Memory: 49276K      Time: 7016MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 4000;

int n, m;
int a[4][N];
int x[N*N], y[N*N];

int main(void)
{
    cin >> n;
    m = n*n;
    for (int i = 0; i < n; i ++)
        for (int j = 0; j < 4; j ++)
            scanf("%d", &a[j][i]);
    for (int i = 0; i < n; i ++)
        for (int j = 0; j < n; j ++)
            x[i*n+j] = a[0][i] + a[1][j];
    for (int i = 0; i < n; i ++)
        for (int j = 0; j < n; j ++)
            y[i*n+j] = a[2][i] + a[3][j];
    sort(y, y+m);

    long long ans = 0;
    for (int i = 0; i < m; i ++)
        ans += (upper_bound(y, y+m, -x[i]) - lower_bound(y, y+m, -x[i]));
    printf("%lld\n", ans);

    return 0;
}

POJ3977

http://poj.org/problem?id=3977

题意

给定N个整数(不超过10^15)组成的数列(N<=35),从中选出一个子集,使得这个子集的所有元素的值的和的绝对值最小,如果有多组数据满足的话,选择子集元素最少的那个。

思路

如果单纯的枚举的话,这N个数分别有选和不选两种,所以一共有2^35个子集。很明显,会超时,但是我们可以将这个整数数列分成两半,可得每边最多18个,如果分别进行枚举的话,复杂度可以降到O((N/2)²),即最多2^18,在可接受范围内。然后枚举其中一个子集,二分查找与他组成绝对值最小的子集。
具体的做法为:

两个数组A和B分别表示前一半元素组成的和以及后一半元素组成的和,数组中的元素用pair<LL, int>表示,LL代表和,int代表几个元素组成的和。对两个数组分别进行排序(第一依据是和的大小,第二依据是元素个数),然后顺序扫描数组,相同和的元素只保留元素个数最小的那个。另外顺便对数组内的元素进行搜索寻找答案。
答案可能位于的另一种情况是前一半和后一半各取一部分元素组成的和,这就可以枚举数组A,然后在数组B中二分查找其相反数,注意这里用的比较函数与排序时的比较函数是不同的:排序时比较函数有两个比较依据,而这里查找时的比较函数只依据和的值。再者,由于我们要找的是绝对值最小,这里用lower_bound查到值(使总和非负但绝对值最小)之后,应该还要对其前一个值(使总和为负但绝对值最小)进行验证。

尽管思路很清晰,实际做的过程中需要注意的地方却有很多,主要见具体做法说明及代码。这个题我大概WA了5次,最后一次WA犯的错误是INF值设置的太小,后来设置成了(1LL<<60)就没问题了。
最后看了一下这个题的AC率,确实很低,大概百分之十几,有的人甚至提交失败十几次。确实是细节决定成败。

代码

Source Code

Problem: 3977       User: liangrx06
Memory: 8460K       Time: 5860MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long LL;
typedef pair<LL, int> PR;

const int N = 35;
const int M = (1<<18);
const LL INF = (1LL<<60);

bool cmp(PR x, PR y)
{
    if (x.first != y.first)
        return x.first < y.first;
    return x.second < y.second;
}

bool cmpVal(PR x, PR y)
{
    return x.first < y.first;
}

LL llabs(LL x)
{
    return x >= 0 ? x : -x;
}

int n2, n[2], m[2];
LL a[N];
PR s[2][M];

int main(void)
{
    while (cin >> n2 && n2)
    {
        for (int i = 0; i < n2; i ++)
            scanf("%lld", &a[i]);

        PR ans = PR(INF, N+1);
        n[0] = n2/2;
        n[1] = n2-n2/2;
        for (int j = 0; j < 2; j ++) {
            m[j] = (1 << n[j]);
            for (int k = 0; k < m[j]; k++) {
                s[j][k].first = s[j][k].second = 0;
                for (int i = 0; i < n[j]; i ++) {
                    if ((k>>i) & 1) {
                        s[j][k].first += a[i + j*n[0]];
                        s[j][k].second ++;
                    }
                }
            }
            sort(s[j]+1, s[j]+m[j], cmp);
            int cnt = 1;
            for (int k = 1; k < m[j]; k++) {
                if (s[j][k].first != s[j][k-1].first) {
                    s[j][cnt++] = s[j][k];
                    PR y(llabs(s[j][k].first), s[j][k].second);
                    if (cmp(y, ans)) ans = y;
                }
            }
            m[j] = cnt;
        }

        for (int k = 1; k < m[1]; k++) {
            PR y(-s[1][k].first, s[1][k].second);
            PR *x = lower_bound(s[0]+1, s[0]+m[0], y, cmpVal);
            for (int j = -1; j <= 0; j++) {
                if (x+j < s[0]+1 || x+j >= s[0]+m[0]) continue;
                y = PR(llabs((x+j)->first + s[1][k].first), (x+j)->second + s[1][k].second);
                if (cmp(y, ans)) ans = y;
            }
        }

        printf("%lld %d\n", ans.first, ans.second);
    }

    return 0;
}

POJ2549

http://poj.org/problem?id=2549

题意

给你一个公式a+b+c=d,让你在同一个集合(元素不同)内满足该条件时d的最大值,注意a b c d均不同。

思路

网上折半搜索的思路一般是将a+b+c=d拆分成 a+b=d-c,然后我没敢再继续看,就自己开始写了。没写到写出了一直TLE或者WA。
后来看了一下网上别人写的代码,感觉自己的也没什么错误啊。然后运行了一下别人的代码,发现时间内存都是0!!!!!!(当然这个代码我发现还有点bug,修改后时间大约十几ms,一会附上代码)
参考别人的代码后我自己的折半搜索代码终于也修改成功了,结果我的数据是“Memory: 15344K Time: 719MS”,差距真心好大!!!
分析了一下,主要原因出在搜a+b上,人家代码是线性O(N)的,我的最坏能达到O(N^N),平均估计也得O(NlogN)吧。
不说了,上代码。。。

代码1(我的折半枚举)

Source Code

Problem: 2549       User: liangrx06
Memory: 15344K      Time: 719MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 1000;
const int INF = 600000000;

struct Sum {
    int i, j;
    int v;
};

bool cmp(Sum x, Sum y)
{
    return x.v < y.v;
}

int n, m;
int A[N];
Sum x[2][N*N];

int main(void)
{
    while (cin >> n && n) {
        m = n*n;
        for (int i = 0; i < n; i ++)
            scanf("%d", &A[i]);
        sort(A, A+n);
        for (int i = 0; i < n; i ++) {
            for (int j = 0; j < n; j ++) {
                int id = i*n+j;
                x[0][id].i = x[1][id].i = i;
                x[0][id].j = x[1][id].j = j;
                x[0][id].v = A[i] + A[j];
                x[1][id].v = A[i] - A[j];
            }
        }
        sort(x[0], x[0]+m, cmp);

        int ans = -INF;
        int a, b, c, d;
        for (int i = m-1; i >= 0; i --) {
            d = x[1][i].i, c = x[1][i].j;
            if (c == d) continue;
            Sum *lp = lower_bound(x[0], x[0]+m, x[1][i], cmp);
            Sum *up = upper_bound(x[0], x[0]+m, x[1][i], cmp);
            for (Sum *p = lp; p < up; p ++) {
                a = p->i, b = p->j;
                if (a == b || a == c || a == d || b == c || b == d)
                    continue;
                //printf("%d %d %d %d\n", A[a], A[b], A[c], A[d]);
                ans = A[d];
                break;
            }
            if (ans > -INF) break;
        }
        if (ans == -INF)
            printf("no solution\n");
        else
            printf("%d\n", ans);
    }

    return 0;
}

代码2(更优的折半枚举)

Source Code

Problem: 2549       User: liangrx06
Memory: 168K        Time: 16MS
Language: C++       Result: Accepted
Source Code
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int inf=-600000000;
int a[1001];
int main()
{
    int n;
    while(scanf("%d",&n)&&n)
    {
        for(int i=0;i<n;i++)
            scanf("%d",&a[i]);
        sort(a,a+n);
        n--;
        int ans=inf;
        for(int i=n;i>=0;i--)
        {
            for(int j=n;j>=0;j--)
            {
                if(i==j)
                    continue;
                int sum=a[i]-a[j];
                for(int l=0,r=j-1;l<r;)
                {
                    if(a[l]+a[r]==sum)
                    {
                        if (l != i && r != i) {
                            ans=a[i];
                            break;
                        }
                    }
                    if(a[l]+a[r]>sum)
                        r--;
                    else
                        l++;
                }
                if(ans!=inf)
                    break;
            }
            if(ans!=inf)
                break;
        }
        if(ans==inf)
            printf("no solution\n");
        else
            printf("%d\n",ans);
    }
    return 0;
}

你可能感兴趣的:(poj,挑战程序设计竞赛,折半枚举)