1. 程式人生 > >[HNOI2006]超級英雄Hero [Scoi2010]遊戲 匈牙利演算法 解題報告

[HNOI2006]超級英雄Hero [Scoi2010]遊戲 匈牙利演算法 解題報告

這兩道題是極為類似的,只是一道 n <1001,另一道 n <10000 好像區別不大
你有n個物品,每個物品有兩個值且每個物品只能使用一次,每次使用可以選擇它的其中一個值。
提問:使用一些物品後,能取到的最大的連續值是多少?

以 [HNOI2006]超級英雄Hero 為例:有六個物品,其對應值分別是
3 2
2 0
0 3
0 4
3 2
3 2
當分別選擇<3,2,0,4>可過前四關,且這也是最優解(之一)

此時可選物品和對應值形成了一個 “二分圖”

二分圖匹配——匈牙利演算法

匈牙利演算法的基本內容是:為該點尋找一個匹配,並且其他已匹配點仍有匹配
定義一個bool f(v) ,v 指新的匹配點,返回能否匹配成功。
先列舉v的每一個未在遞迴過程中被搜尋過的節點,如果其未被匹配,則匹配,否則呼叫f(其連線節點)以重新分配。

核心程式碼:

bool f(int v) {
    for (int i=0;i<u[v].size();i++)
    if (!vis[rt = u[v][i]]) {
        vis[rt] = true;
        if ((!q[rt]) || f(q[rt])) return q[rt] = v;
    } return false;
}

[HNOI2006]超級英雄Hero :

#include <cstdio>
#include <cstring>
#include <vector>

int n,m,i,q[1010]
,rt,t1,t2; bool vis[1010]; std::vector <int> u[1010]; bool f(int v) { for (int i=0;i<u[v].size();i++) if (!vis[rt = u[v][i]]) { vis[rt] = true; if ((!q[rt]) || f(q[rt])) return q[rt] = v; } return false; } int main() { scanf("%d%d",&n,&m); for (i=1;i<=m
;i++) { scanf("%d%d",&t1,&t2); u[i].push_back(t1); u[i].push_back(t2); } for (i=1;i<=m;i++) if (memset(vis,0,sizeof(vis)) && !f(i)) break; printf("%d\n",i - 1); }

[Scoi2010]遊戲:(這題memset會超時 於是開int vis[]記錄)

#include <cstdio>
#include <cstring>

int n,i,q[1000010],head[1000010],last[1000010],rt,t1,t2,ans,num;
int vis[1000010],id;
struct ljb {int to,nxt;} u[2000010];

bool f(int v) {
    for (int i=head[v];i;i=u[i].nxt) if (vis[rt = u[i].to] ^ id) {
        vis[rt] = id;
        if ((!q[rt]) || f(q[rt])) return q[rt] = v;
    } return false;
}

int main() {
    for (scanf("%d",&n),i=1;i<=n;i++) {
        scanf("%d%d",&t1,&t2);
        u[++num].to = i;  last[t1] = last[t1] ? u[last[t1]].nxt = num : head[t1] = num;
        u[++num].to = i;  last[t2] = last[t2] ? u[last[t2]].nxt = num : head[t2] = num;
    } while (f(id = ++ans));
    printf("%d\n",ans - 1);
}