2014携程初赛第二场

今年似乎出了很多POJ的原题。。

第二场我没参加, 比赛的时候看别人的题目, 大概看了下A,想了下B。

当时觉得A是并查集 + 拓扑判环(呵呵), 后来再次看到题目的时候真是亮瞎了,真是不能再经典的一道并查集,食物链那道并查集当初做的时候就是参考别人思路想了很久, 今天又自己想了一遍, 感觉依然不好推公式。

然后学弟让我看B的时候, 我觉得就是简单的搜一下判断。后来想想搜索依然可能爆掉。。那干脆记忆化搜索吧,防止各种重复搜。

C是一道简单的模拟题, 倒序判断即可。

D是一道博弈, 我还不会做。


A题, 经典并查集说下思路吧。

这种带权的并查集的最大的特点就是不仅要记录祖先是谁, 而且由于各个节点之间存在多种关系, 需要一个权值将他们区分开。 在这道题目中,设定一个rank数组, 假设fa[X] = Y, 在这道题目中我们可以约定:rank[x] = 0,表示X跟他所在的集合的根节点Y之间的关系是平等的。rank[X] = 1, 表示Y可以吃X, rank[X]= 2, 表示X可以吃Y。

在这种假设下, 我们发现任意两个集合的两个元素都可以通过自己与根节点之间的关系去传递。

比如fa[x] = p, fa[y] = q, 已知rank[x] = 0, rank[y] = 0;如果p=q,表示x,y在一个集合,rank[x] = rank[y] = 0, 表示x,y与他们的根根节点是平等的,可知x,y之间也是平等的。 这只是这当中最简单的一种关系传递。

比如路径压缩的时候要及时更新当前节点与根节点的关系, 如何更新? 可以通过自己的父亲这一渠道。假设X节点, 父亲为Y,根为Z,  现在假设已经知道Y被Z吃, X与Y平等, 那显然X也要被Z吃, 通过这种传递我们就建立了X与Z的关系。比较复杂的是要推导根节点不同的时候合并的关系。 具体办法就是列出各种状态然后找到一个通式。


//A
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 1e4 + 10;
int fa[N], rank[N];
int n, k;

int find(int x){
    int tmp = fa[x];
    if(fa[x] != x) fa[x] = find(fa[x]);
    rank[x] = (rank[tmp] + rank[x]) % 3;
    return fa[x];
}

void unit(int x, int y, int fx, int fy, int d){
    fa[fy] = fx;
    rank[fy] = (rank[x] - rank[y] + d + 2) % 3;
}

int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        int ans = 0;
        scanf("%d%d", &n, &k);
        for(int i = 0; i <= n; ++i){
            fa[i] = i;
            rank[i] = 0;
        }
        for(int i = 0; i < k; ++i){
            int d, x, y;
            scanf("%d%d%d", &d, &x, &y);
            if((d == 2 && x == y) || (x > n) || (y > n)){
                ++ans;
                continue;
            }
            int fx = find(x), fy = find(y);
            if(fx == fy){
                if(d == 1){
                    if(rank[x] != rank[y]){
                        ++ans;
                    }
                }
                else{
                    if(rank[x] != (rank[y] + 2) % 3)
                        ++ans;
                }
            }
            else unit(x, y, fx, fy, d);
        }
        printf("%d\n", ans);
    }
    return 0;
}


B较为简单。 只需要DP出所有可行的状态再比较就OK了。

//B
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 1e3;
int a[N], f[N][N], n;

double Cal(int a, int b, int c){
    double p = (double)(a + b + c)/2;
    return sqrt(p*(p-a)*(p-b)*(p-c));
}

void gao(int v){
    memset(f, 0, sizeof(f));
    f[0][0] = 1;
    for(int i = 0; i < n; ++i){
        for(int j = v; j >= 0; --j){
            for(int k = v; k >= 0; --k){
                if(j >= a[i] && f[j-a[i]][k]) f[j][k] = 1;
                else if(k >= a[i] && f[j][k-a[i]]) f[j][k] = 1;
            }
        }
    }
}

int main(){
    while(~scanf("%d", &n)){
        if(n == 0) break;
        int sum = 0, v;
        for(int i = 0; i < n; ++i){
            scanf("%d", &a[i]);
            sum += a[i];
        }
        v = sum >> 1;
        gao(v);

        double ans = -1;
        for(int i = 1; i <= v; ++i){
            for(int j = 1; j <= v; ++j){
                if(f[i][j]){
                    int k = sum - i - j;
                    if(i+j > k && i+k > j && j+k > i){
                        double ret = Cal(i, j, k);
                        if(ret > ans) ans = ret;
                    }
                }
            }
        }
        if(ans < 0) printf("-1\n");
        else printf("%d\n", (int)(ans*100));
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值