ZOJ 3316 Game 一般图匹配

来源:互联网 发布:淘宝指南针店铺 编辑:程序博客网 时间:2024/05/22 17:29

题目:

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=3726

题意:

棋盘上有n个棋子,两个人轮流从棋盘上取走一个棋子,取子规则是当前要取的棋子与上一颗被取走的棋子的曼哈顿距离不得超过L,最后无子可取的人输。假设两人都采取最佳策略,问后手能不能赢得游戏

思路:

首先,若棋盘上有一颗及以上的棋子距离其他所有棋子的曼哈顿距离超过了L,那么后手必输。否则,对这些棋子进行建图,曼哈顿距离小于等于L的棋子连无向边,求出一般图的最大匹配,可以发现,对于匹配的两颗棋子,先手如果取走其一,后手只需要取另一颗,当只剩下不匹配的棋子时,先手随意取,后手必无子可取,因此,如果最大匹配*2 = 棋子数,后手赢,否则必输

#include <iostream>#include <cstdio>#include <cstring>#include <cmath>using namespace std;const int N = 410;struct edge{    int to, next;}g[N*N*2];int cnt, head[N];int que[N], rear;int match[N], mark[N], nxt[N], par[N], vis[N];void add_edge(int v, int u){    g[cnt].to = u, g[cnt].next = head[v], head[v] = cnt++;}int ser(int x){    int r = x, i = x, j;    while(r != par[r]) r = par[r];    while(i != r) j = par[i], par[i] = r, i = j;    return r;}void unite(int x, int y){    x = ser(x), y = ser(y);    if(x == y) return;    par[x] = y;}int lca(int x, int y){    static int t = 0;    t++;    while(true)    {        if(x != -1)        {            x = ser(x); //点要对应到所在的花上去            if(vis[x] == t) return x;            vis[x] = t;            if(match[x] != -1) x = nxt[match[x]];            else x = -1;        }        swap(x, y); //交换,使两点轮流走    }}void group(int x, int r){    while(x != r)    {        int y = match[x], z = nxt[y];        if(ser(z) != r) nxt[z] = y;        if(mark[y] == 2) mark[que[rear++] = y] = 1;        if(mark[z] == 2) mark[que[rear++] = z] = 1;        unite(x, y), unite(y, z);        x = z;    }}void augmented(int n, int s){    for(int i = 1; i <= n; i++)        nxt[i] = -1, par[i] = i, mark[i] = 0, vis[i] = -1;    mark[s] = 1;    que[0] = s;    rear = 1;    for(int i = 0; match[s] == -1 && i < rear; i++)    {        int v = que[i];        for(int j = head[v]; j != -1; j = g[j].next)        {            int u = g[j].to;            if(match[v] == u) continue; //v,u已经匹配,跳过            if(ser(v) == ser(u)) continue; //在同一朵花中,跳过            if(mark[u] == 2) continue; //T型,跳过            if(mark[u] == 1) //S型,奇环缩点            {                int r = lca(v, u); // r为从v和u到s的路径上的第一个公共节点                if(ser(v) != r) nxt[v] = u; // r和v不在同一个花朵,nxt标记花朵内路径                if(ser(u) != r) nxt[u] = v; // r和u不在同一个花朵,nxt标记花朵内路径                //将整个r-x-y-r的奇环缩成点,r作为这个环的标记节点                group(v, r), group(u, r);// 缩路径r-v和r-u为点            }            else if(match[u] == -1) //u自由,可以增广            {                nxt[u] = v;                for(int k = u; k != -1;) //交错路取反                {                    int x = nxt[k];                    int tm = match[x];                    match[x] = k, match[k] = x;                    k = tm;                }                break; // 搜索成功,退出循环将进入下一阶段            }            else            { // 当前搜索的交叉链+u+match[u]形成新的交错路,将match[u]加入队列作为待搜节点                nxt[u] = v;                mark[que[rear++] = match[u]] = 1;                mark[u] = 2;            }        }    }}int work(int n){    memset(match, -1, sizeof match);    for(int i = 1; i <= n; i++)        if(match[i] == -1) augmented(n, i);    int num = 0;    for(int i = 1; i <= n; i++)        if(match[i] != -1) num++;    return num/2;}int main(){    int n, l;    int a[N], b[N];    bool ing[N];    while(~ scanf("%d", &n))    {        cnt = 0;        memset(head, -1, sizeof head);        memset(ing, 0, sizeof ing);        for(int i = 1; i <= n; i++) scanf("%d%d", &a[i], &b[i]);        scanf("%d", &l);        for(int i = 1; i <= n; i++)            for(int j = i+1; j <= n; j++)                if(abs(a[i]-a[j]) + abs(b[i]-b[j]) <= l)                {                    add_edge(i, j), add_edge(j, i);                    ing[i] = ing[j] = true;                }        bool flag = true;        for(int i = 1; i <= n; i++)            if(! ing[i])            {                flag = false; break;            }        if(! flag) puts("NO");        else        {            int num = work(n);            if(n == num * 2) puts("YES");            else puts("NO");        }    }    return 0;}
0 0
原创粉丝点击