多校第一场I Curse Myself(HDU6041)

此题是我第一次接触仙人掌图,第一次了解Tarjan算法,也是第一次了解了合并N个集合的解法,这么多第一次,当然要记录一下。

Tarjan算法的介绍已经在我的另一篇博客中做了介绍(tarjan算法详解),这里解不在多讲了。

还有仙人掌图的大概介绍一下,就是图中的每一条边都唯一属于一个简单环路,不清楚的可以自行百度。

这里我主要想讲的就是如何合并N个集合,求出在这N个集合任取一个数求和得到前K大个数,做法就是对集合两两合并,每次合并时得到的新集合只保存前K大的数,因为第K大的数之后的数和其他集合合并时不可能超过前K个数的结果,然后保证得到的这K个数是有序的,方便下次合并。

在具体实现中用到了优先队列(大根堆),假设要合并集合A,B, A是从大到小有序排列的,首先将A中第一个数(即最大的数)和要B集合的每一个数相加然后将结果扔到优先队列中,同时每一个结果要保留由A,B中那两个元素合并合成,这样优先队列的的第一个元素一定是两个集合合并能产生的最大的结果,然后每取出一个结果,因为A集合是有序的,因此将这个结果中A的元素后移以为,即用A中合并成这个结果的A中元素的后一个元素来替换,这样得到一个小于等于取出的这个结果的数,在放入优先队列中,如此得到前K大的结果即可。

 

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 1010;//点数
const int MAXE = 4020;//边数
const long long mod = (1LL << 32);

struct Edge
{
    int to, next, cost;
}edge[MAXE];

struct node
{
    int id1, id2, value;
    bool operator < (const node& ano) const
    {
        return value < ano.value;
    }
};

int head[MAXN], tot;
int low[MAXN], dfn[MAXN], Stack[MAXN];
int index, top;
int K;
int res[100005], rt[100005], tmp[100005];
bool vis[MAXE];

void addedge(int u, int v, int _cost)
{
    edge[tot].to = v;
    edge[tot].cost = _cost;
    edge[tot].next = head[u];
    head[u] = tot++;
}

void unit(int* a, int *b)//a是从大到小排序的
{
    tmp[0] = 0;
    priority_queue<node>que;
    for(int i = 1; i <= b[0]; ++i)
        que.push((node){1, i, a[1] + b[i]});
    while(tmp[0] < K && !que.empty())
    {
        node nn = que.top();
        que.pop();
        tmp[++tmp[0]] = nn.value;
        if(nn.id1 + 1 <= a[0])
        {
            nn.id1 += 1;
            que.push((node){nn.id1, nn.id2, a[nn.id1] + b[nn.id2]});
        }
    }
    for(int i = 0; i <= tmp[0]; ++i)
        a[i] = tmp[i];
    return;
}

void Tarjan(int u, int fa)
{
    dfn[u] = low[u] = index++;
    vis[u] = true;
    for(int i = head[u]; ~i; i = edge[i].next)
    {
        int v = edge[i].to;
        if(v == fa) continue;
        if(!vis[v])
        {
            Stack[top++] = i;
            Tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] >= dfn[u])
            {
                rt[0] = 0;
                int p = 0;
                do
                {
                    p = Stack[--top];
                    rt[++rt[0]] = edge[p].cost;
                }
                while(p != i);
                if(rt[0] > 1)
                    unit(res, rt);
            }
        }
        else if(low[u] > dfn[v])
        {
            Stack[top++] = i;
            low[u] = min(low[u], dfn[v]);
        }
    }
    return;
}

int main()
{
    //freopen("1009.in", "r", stdin);
    //freopen("out.out", "w", stdout);
    int cas = 1;
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF)
    {
        memset(head, -1, sizeof(head));
        memset(dfn, 0, sizeof(dfn));
        memset(low, 0, sizeof(low));
        memset(vis, 0, sizeof(vis));
        index = top = 0;
        int u, v, c;
        long long sum = 0LL;
        tot = 0;
        while(m--)
        {
            scanf("%d%d%d", &u, &v, &c);
            sum = sum + c;
            addedge(u, v, c);
            addedge(v, u, c);
        }
        scanf("%d", &K);
        res[0] = 1;
        res[1] = 0;
        Tarjan(1, -1);
        long long ans = 0;
        for(int i = 1; i <= res[0]; ++i)
        {
            //printf("%d%c", res[i], i == res[0] ? '\n' : ' ');
            ans = (ans + (1LL * i * (sum - res[i])) % mod) % mod;
        }
        printf("Case #%d: %lld\n", cas++, ans);
    }
    return 0;
}

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值