2017暑期集訓 Day 3 搜索與並查集

A - 食物鏈

[solution]
並查集新操作!
維護決策的矛盾信息,每次決策之間的選擇是互相矛盾的
每個動物可能爲三種情況:A、B、C,i代表第i種動物是A類動物,i+n代表第i種動物是B類,i+2*n是c類,這樣
對於d=1時,x、y爲同類動物,即如果x是a類,則y也爲a類,即unite(x,y),同理,都爲b類,unite(x+n,y +n),同理,unite(x+2*n,y+2*n)
對於d=2時,x吃y,如果x是a類,則y是b類,即unite(x, y + n),同理unite(x + n, y + 2*n), unite(x +2*n, y),下面考慮矛盾的情況,對於d=1時,x、y會有九種狀態,出去最後操作的三種狀態,另外六種狀態都是不允許出現的,即(x, y), (x + n, y + n), (x + 2n, y +2n), (x, y + 2n),(x + n, y), (x + 2n, y + n)難道我們必須打6個if嗎?我們觀察這六種狀態,前三種是等價的,真假值相同,這樣我們每次判斷兩個即可
d=2類似

[Code]

#include<cstdio>
#include<iostream>
#include<set>
using namespace std;
const int N = 200000 + 500;
int f[N];
int n, m;
int find(int x)
{
    if (f[x] == x)
        return x;
    return f[x] = find(f[x]);
}
void unio(int x, int y)
{
    int xx = find(x);
    int yy = find(y);
    if (xx != yy)
        f[xx] = yy;
}
bool cal(int x, int y)
{
    int xx = find(x);
    int yy = find(y);
    return xx == yy;
}
int main()
{
   // freopen("a.in", "r", stdin);
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n * 3; i++)
        f[i] = i;
    int ans = 0;
    for(int i = 1; i <= m; i++)
    {
        int d, x, y;
        scanf("%d%d%d", &d, &x, &y);
        if (x < 1 || x > n || y < 1 || y > n)
        {
            ans++;
            continue;
        }
        if (d == 1)
        {
            if (cal(x, y + n) || cal(x, y + 2 * n))
            {
                ans++;
                continue;
            }
            unio(x, y);
            unio(x + n, y + n);
            unio(x + 2 * n, y + 2 * n);
        }
        else
        {
            if (x == y)
            {
                ans++;
                continue;
            }
            if (cal(x, y) || cal(x, y + 2 * n))
            {
                ans++;
                continue;
            }
            unio(x, y + n);
            unio(x + n, y + 2 * n);
            unio(x + 2 * n, y);
        }
    }
    printf("%d", ans);
    return 0;
}

B - Wireless Network

[Solution]
一個樸素的二分
[Code]

#include<cstdio>
#include<iostream>
using namespace std;
const int N= 200000;
int f[N];
int n, m;
bool rep[N];
double x[N], y[N];
int find(int x)
{
    if (f[x] == x)
        return x;
    return f[x] = find(f[x]);
}
int union1(int x, int y)
{
   // printf("%d %d\n", x, y);
    int xx = find(x), yy = find(y);
    if (xx != yy)
        f[xx] = yy;
}
int main()
{
   // freopen("a.in", "r", stdin);
    int n;
    double d;
    scanf("%d", &n);
    scanf("%lf", &d);
    for(int i = 1; i <= n; i++)
        scanf("%lf%lf", &x[i], &y[i]);
    for(int i = 1; i <= n; i++)
        f[i] = i;
    string s;
    while(cin>>s)
    {
        if (s == "O")
        {
            int k;
            scanf("%d", &k);
            for(int i = 1; i <= n; i++)
                if (k != i)
            {
                rep[k] = true;
                double dis = (x[i] - x[k]) * (x[i] - x[k]) + (y[i] - y[k]) * (y[i] - y[k]);
                if (rep[i] && dis <= d * d)
                    union1 (i, k);
            }
        }
        else
        {
            int i, j;
            scanf("%d%d", &i, &j);
            int ii = find(i);
            int jj = find(j);
            if (ii == jj)
                printf("SUCCESS\n");
            else
                printf("FAIL\n");
        }
    }
    return 0;
}

C - The Door Problem

[Solution]
這個思路和A類似,對於每個門都對應兩份switch,每個switch有兩種狀態,這樣我們對於每個門進行分析,如果此門的狀態是鎖住的,我們這兩個開關分別是一開一關,因此unite(x,y +n)
|unite(x+n,y), 反之則同時開啓或同時關閉
[Code]

#include<cstdio>
#include<iostream>
using namespace std;
const int N = 200000 + 500;
int f[N];
int a[N][5], n, m, tot[N];
int find(int x)
{
    if (f[x] == x)
        return x;
    return f[x] = find(f[x]);
}
void unio(int x, int y)
{
    int xx = find(x);
    int yy = find(y);
    if (xx != yy)
        f[xx] = yy;
}
bool cal(int x, int y)
{
    int xx = find(x);
    int yy = find(y);
    return xx == yy;
}
int main()
{
   // freopen("a.in", "r", stdin);
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        scanf("%d", &tot[i]);
    for(int i = 1; i <= 2 * m; i++)
        f[i] = i;
    for(int i = 1; i <= m; i++)
    {
        int top;
        scanf("%d", &top);
        for(int j = 1; j <= top; j++)
        {
            int y;
            scanf("%d", &y);
            if (a[y][0] != 0)
                a[y][1] = i;
            else
                a[y][0] = i;
        }
    }
    for(int i = 1; i <= n; i++)
    {
        int x = a[i][0], y = a[i][1];
        if (tot[i] == 0)
        {
            if (cal(x, y) || cal(x + m, y + m))
            {
                printf("NO");
                return 0;
            }
            unio(x, y + m);
            unio(x + m, y);
        }
        else
        {
            if (cal(x, y + m) || cal(x + m, y))
            {
                printf("NO");
                return 0;
            }
            unio(x, y);
            unio(x + m, y + m);
        }
    }
    printf("YES");
    return 0;
}

D - 小希的迷宮

[Solution]
判斷一副圖是不是樹,此題坑點很多。。。
首先很容易想到每次加邊的時候判斷兩端點所處的集合是否相同,,,但是最後你還得確保此圖聯通,所以判斷一下點的個數,與邊的個數相比較一下即可
注意00的情況
[Code]

#include<cstdio>
#include<iostream>
#include<set>
using namespace std;
const int N = 200000 + 500;
int f[N];
int s[N], t[N], top;
set<int> point;
int find(int x)
{
    if (f[x] == x)
        return x;
    return f[x] = find(f[x]);
}
void unio(int x, int y)
{
    int xx = find(x);
    int yy = find(y);
    if (xx != yy)
        f[xx] = yy;
}
bool cal(int x, int y)
{
    int xx = find(x);
    int yy = find(y);
    return xx == yy;
}
bool calc()
{
    for(int i = 1; i <= top; i++)
    {
        int x = s[i], y = t[i];
        if (cal(x, y))
            {
         //        printf("This is %d %d \n", x, y);
                return false;
            }
        if (point.count(x) == 0)
            point.insert(x);
        if (point.count(y) == 0)
            point.insert(y);
        unio(x, y);
    }
    if (point.size() != top + 1)
        return false;
    return true;
}
int main()
{
   // freopen("a.in", "r", stdin);
    int x, y;
    top = 0;
    while(~scanf("%d%d", &x, &y))
    {
        if (x == -1 && y == -1)
            break;
        if (x != 0)
        {
            top++;
            s[top] = x;
            t[top] = y;
        }
        else
        {
            if (top == 0)
                {
                    printf("Yes\n");
                    continue;
                }
            for(int i = 1; i <= 100000; i++)
                f[i] = i;
            if (calc())
                printf("Yes\n");
            else
                printf("No\n");
            top = 0;
            point.clear();
        }
    }
    return 0;
}

E - Island Puzzle

[Solution]
我們注意到每次只能把數字移動到0的位置,這樣這些數字之間的相對順序是不會改變的,這樣我們讀取的時候忽略掉0,把a串寫兩遍,判斷b串是否是a串的一個子串即可
[Code]

#include<cstdio>
#include<iostream>
using namespace std;
const int N= 400000 + 500;
int a[N], b[N];
int n;
bool calc(int l, int r)
{
    int x = l, y = 1;
    for(int i = 1; i <= n; i++)
    {
        if (a[x] != b[y])
            return false;
        x++; y++;
    }
    return true;
}
int main()
{
    //freopen("a.in", "r", stdin);
    scanf("%d", &n);
    int top = 0;
    for(int i = 1; i <= n; i++)
    {
       int x;
       scanf("%d", &x);
       if (x != 0)
          a[++top] = x;
    }
    top = 0;
    for(int i = 1; i <= n; i++)
    {
       int x;
       scanf("%d", &x);
       if (x != 0)
          b[++top] = x;
    }
    n--;
    for(int i = 1; i <= n; i++)
        a[i + n] = a[i];
    for(int i = 1; i <= n; i++)
        if (a[i] == b[1])
    {
        if (calc(i, i + n - 1))
            printf("YES");
        else
            printf("NO");
        return 0;
    }
    return 0;
}

F - The Tag Game

[Solution]
很有意思的一道題目,顯然,最後相遇到葉子節點,我們跑一邊樹形dp,預處理每個結點到以此節點爲根的子樹的最大路徑f[i],這樣我們跑一遍第一個人能到達的結點i,ans=max(f[i] + dis[i]),disp[i]爲根到i的距離
[Code]

#include<cstdio>
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
const int N= 400000 + 500;
int dis[N], dp[N], par[N];
bool f[N];
vector<int> a[N];
vector<int> tree[N];
int n, m;
void build(int x)
{
    for(int i = 0; i < a[x].size(); i++)
    {
        int y = a[x][i];
        if (f[y])
            continue;
        tree[x].push_back(y);
        par[y] = x;
        f[y] = true;
        build(y);
    }
}
void treedp(int x)
{
    if (a[x].size() == 0)
    {
        dp[x] = 0;
        return ;
    }
    for(int i = 0; i < tree[x].size(); i++)
    {
        int y = tree[x][i];
        treedp(y);
        dp[x] = max(dp[x], dp[y] + 1);
    }
}
int main()
{
   // freopen("a.in", "r", stdin);
    scanf("%d%d", &n, &m);
    for(int i = 1; i < n; i++)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        a[x].push_back(y);
        a[y].push_back(x);
    }
    f[1] = true;
    build(1);
    treedp(1);
    int x = m;
    int ans = 0;
    while(x != 1)
    {
        ans++;
        x = par[x];
    }
    int tot = -1, answer = 0;
    x = m;
    while(x != 1)
    {
        tot++;
        if (tot >= ans - tot)
            break;
        answer = max(answer, ans - tot + dp[x]);
      //*  printf("%d\n", x);
        x = par[x];
    }
    printf("%d", answer * 2);
    return 0;
}

G - Mike and Shortcuts

[Solution]
很裸的bfs
[Code]

#include<cstdio>
#include<iostream>
#include<queue>
using namespace std;
const int N= 400000 + 500;
int a[N], dis[N], tle[100];
int n, s, t;
bool f[N];
queue<int >q;
int main()
{
    //freopen("a.in", "r", stdin);
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for(int i = 1; i <= n; i++)
        dis[i] = n + 250;
    dis[1] = 0;
    q.push(1);
    f[1] = true;
    while(!q.empty())
    {
        int x = q.front();
        q.pop();
        tle[1] = x - 1;
        tle[2] = x + 1;
        tle[3] = a[x];
        for(int i = 1; i <= 3; i++)
        {
            int y = tle[i];
            if (y < 1 || y > n)
                continue;
            if (!f[y] && dis[y] > dis[x] + 1)
            {
                dis[y] = dis[x] + 1;
             //   printf("%d -- >  %d    %d\n", x, y, dis[y]);
                f[y] = true;
                q.push(y);
            }
        }
    }
    printf("0");
    for(int i = 2; i <= n; i++)
        printf(" %d", dis[i]);
    return 0;
}

H - A strange lift

[Solution] dp 搞一下,用時間作爲順序即可
[Code]

#include<cstdio>
#include<iostream>
using namespace std;
const int N= 400000 + 500;
int f[N], a[N];
int n, s, t;
int main()
{
    //freopen("a.in", "r", stdin);
    while(~scanf("%d", &n) && n)
    {
        scanf("%d%d", &s, &t);
        for(int i = 1; i <= n ; i++)
            scanf("%d", &a[i]);
        for(int i = 1; i <= n; i++)
            f[i] = -1;
        f[s] = 0;
        for(int i = 1; i <= 300; i++)
        {
            bool delta = false;
            for(int j = 1; j <= n; j++)
                if (f[j] >= 0)
            {
                int x = j - a[j];
                if (x >= 1 && (f[x] == -1 || f[x] > f[j] + 1))
                    {
                        delta = true;
                        f[x] = f[j] + 1;
                    }
                x = j + a[j];
                if (x <= n && (f[x] == -1 || f[x] > f[j] + 1))
                    {
                        delta = true;
                        f[x] = f[j] + 1;
                    }
            }
            if (!delta)
                break;
        }
        printf("%d\n", f[t]);
    }
    return 0;
}

總結

今天get到並查集的新操作,下午做題狀態不是很好,但是晚上自己在一個陌生的環境裏,狀態好的出奇,昨晚打的CF掉分了,。。。D題竟然爆掉了int,  難過
希望自己在暑期集訓收貨更大吧
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章