大一下第十三周学习笔记

上周的后面几天考试作业什么的导致没怎么训练

这周要回归,火力全开。

最近两周的训练任务就是队内训练赛和三个kuangbin专题

我现在的训练分两条线,一条是队内的比赛和任务,一条是自己学额外的知识点

队内的任务优先

 

周二 5.25(匹配)

昨天去打班级篮球赛去了,所以晚上没训练

队里布置了kuangbin的匹配问题,搞起

A - Fire Net(建图 + 最大匹配)

选最多的点,最大匹配,所以匹配对应点,所以一个点就是一条边

常规的思路是(x, y)是x向y连一条边,这样如果这个点选了,那么x和y都不能被其他边选,也就是不能被其他点选,是符合题意的

但是这道题有个x的存在,导致了一行可以选多个点

那很简单,比如中间有一个x,那就要区分一下

因此可以分配编号,当遇到x的时候就编号++来区分,每次选一个点占领的是对应编号的点

然后跑二分图最大匹配就好了

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 10;
int x[N][N], y[N][N], vis[N * N], link[N * N], n;
vector<int> g[N * N];
char s[N][N];

bool dfs(int u)
{
    for(int v: g[u])
    {
        if(vis[v]) continue;
        vis[v] = 1;
        if(!link[v] || dfs(link[v]))
        {
            link[v] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    while(scanf("%d", &n) && n)
    {
        memset(link, 0, sizeof(link));
        _for(i, 1, n) scanf("%s", s[i] + 1);

        int idx = 0;
        _for(i, 1, n)
        {
            idx++;
            _for(j, 1, n)
            {
                if(s[i][j] == 'X') idx++;
                else x[i][j] = idx;
            }
        }

        int idy = 0;
        _for(j, 1, n)
        {
            idy++;
            _for(i, 1, n)
            {
                if(s[i][j] == 'X') idy++;
                else y[i][j] = idy;
            }
        }

        _for(i, 1, idx) g[i].clear();
        _for(i, 1, n)
            _for(j, 1, n)
                if(s[i][j] != 'X')
                    g[x[i][j]].push_back(y[i][j]);

        int ans = 0;
        _for(i, 1, idx)
        {
            memset(vis, 0, sizeof(vis));
            if(dfs(i)) ans++;
        }
        printf("%d\n", ans);
    }

	return 0;
}

B - The Accomodation of Students(二分图判定 + 最大匹配)

开局不错,顺利连A两题

一看是一个很裸的最大匹配

但是要线判断是否为二分图

这个用染色法就好了,一直染色,如果是二分图那么这么染色下去是不会冲突的

如果冲突了那就不是二分图了

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 210;
int vis[N], link[N], color[N], n, m;
vector<int> g[N];

bool Dfs(int u)
{
    for(int v: g[u])
    {
        if(vis[v])
        {
            if(color[u] == color[v]) return false;
            continue;
        }
        vis[v] = 1;

        color[v] = color[u] ^ 1;
        if(!Dfs(v)) return false;
    }
    return true;
}

bool dfs(int u)
{
    for(int v: g[u])
    {
        if(vis[v]) continue;
        vis[v] = 1;
        if(!link[v] || dfs(link[v]))
        {
            link[v] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    while(~scanf("%d%d", &n, &m))
    {
        _for(i, 1, n) g[i].clear();
        memset(color, 0, sizeof(color));
        memset(link, 0, sizeof(link));
        memset(vis, 0, sizeof(vis));

        while(m--)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
        }

        bool ok = 1;
        _for(i, 1, n)
        {
            if(!vis[i] && !Dfs(i))
            {
                puts("No");
                ok = 0;
                break;
            }
        }
        if(!ok) continue;

        int ans = 0;
        _for(i, 1, n)
            if(color[i])
            {
                memset(vis, 0, sizeof(vis));
                if(dfs(i)) ans++;
            }
        printf("%d\n", ans);
    }

	return 0;
}

C - Courses(二分图最大匹配)

其实非常裸

但是我被惯性思维框住了,我一直觉得二分图的节点是同一类的东西

我一直再考虑把所有课程看成点另一个看成边,或者所有学生看成点,课程看成边

实际上直接二分图左边是课程,右边是学生,直接跑二分图最大匹配就好了

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 310;
int vis[N], link[N], n, p;
vector<int> g[N];

bool dfs(int u)
{
    for(int v: g[u])
    {
        if(vis[v]) continue;
        vis[v] = 1;
        if(!link[v] || dfs(link[v]))
        {
            link[v] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        memset(link, 0, sizeof(link));
        scanf("%d%d", &p, &n);
        _for(i, 1, p)
        {
            g[i].clear();
            int t; scanf("%d", &t);
            while(t--)
            {
                int x; scanf("%d", &x);
                g[i].push_back(x);
            }
        }

        int ans = 0;
        _for(i, 1, p)
        {
            memset(vis, 0, sizeof(vis));
            if(dfs(i)) ans++;
        }
        puts(ans == p ? "YES" : "NO");
    }

	return 0;
}

周三 5.26

Gym-103049A(完全背包 + 贪心)

训练赛补题最后一道

天啊这个官方题解真的把这个题复杂了N多倍

想复杂了很多,把我带偏了……

就是一个完全背包的基础上拓展

给出体积为1到n的物品,价值为a[1] 到a[n]

1.当体积<=n的时候价值是固定的,大于n的时候可以由多个物品组成。

2.询问给出一个体积,求价值最小。这题的询问非常大,大到1e9

 

首先第一个<=n的时候价值是固定的

这个很好解决,直接初始化好1到n的dp值,然后转移就从n + 1开始就好

 

第二个问题是这个询问的体积非常大

如果比较小那么这道题就做完了

这么办呢,我自己想的时候就卡在这

这里利用到了部分的贪心

一开始写背包的时候,有一个错误的贪心,就是每次选性价比最高的物品

这个贪心只有在总体积刚好是此物品体积的倍数的时候才成立,而这道题就利用了这个性质

我们可以部分的贪,也就是说把询问的总体积分为两部分

一部分是上面说的贪心,一部分是完全背包

也就是说利用这个贪心使得总体积减少到预处理的dp的范围之内

就这样就ok了,官方题解还说了什么鸽巢定理什么东西的……

所以直接预处理出1e5(其实远远不用这么大)前的dp值,然后对于每个询问,先用那个贪心来缩小体积,然后再用上dp值就好了

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 110;
const int M = 1e5 + 10;
ll a[N], dp[M];
int n, q, m;

int main()
{
    scanf("%d%d", &n, &q);
    _for(i, 1, n) scanf("%lld", &a[i]);

    double mi = 1e18;
    _for(i, 1, n)
        if((double)a[i] / i < mi)
        {
            mi = (double)a[i] / i;
            m = i;
        }

    memset(dp, 0x3f, sizeof(dp));
    _for(i, 1, n) dp[i] = a[i];
    _for(i, n + 1, 1e5)
        _for(j, 1, n)
            dp[i] = min(dp[i], dp[i - j] + a[j]);

    while(q--)
    {
        int x; scanf("%d", &x);
        if(x <= 1e5) printf("%lld\n", dp[x]);
        else
        {
            int k = (x - 1e5 + m - 1) / m;
            printf("%lld\n", dp[x - k * m] + k * a[m]);
        }
    }

    return 0;
}

HDU 1281

这题我弄错了一直以为暴力删边会超时

实际证明我想多了,是不会超时的

直接暴力删边就好

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 110;
int vis[N], link[N], t[N], n, m, k, a, b;
vector<int> g[N];

bool dfs(int u)
{
    for(int v: g[u])
    {
        if(vis[v] || a == u && b == v) continue;
        vis[v] = 1;
        if(!link[v] || dfs(link[v]))
        {
            link[v] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    int kase = 0;
    while(~scanf("%d%d%d", &n, &m, &k))
    {
        memset(link, 0, sizeof(link));
        _for(i, 1, n) g[i].clear();
        a = b = 0;

        while(k--)
        {
            int x, y;
            scanf("%d%d", &x, &y);
            g[x].push_back(y);
        }

        int ans = 0;
        _for(i, 1, n)
        {
            memset(vis, 0, sizeof(vis));
            if(dfs(i)) ans++;
        }

        int ans2 = 0;
        _for(i, 1, m) t[i] = link[i];
        _for(v, 1, m)
            if(link[v])
            {
                a = link[v], b = v;
                int res = 0;
                memset(link, 0, sizeof(link));
                _for(i, 1, n)
                {
                    memset(vis, 0, sizeof(vis));
                    if(dfs(i)) res++;
                }
                if(res != ans) ans2++;
                _for(i, 1, m) link[i] = t[i];
            }

        printf("Board %d have %d important blanks for %d chessmen.\n", ++kase, ans2, ans);
    }

	return 0;
}

 hdu 2819

这道题搞了我巨久

首先可以逆推,从对角线开始变换,会发现一个条件

但是我把这个条件描述为了每一行和每一列都至少有一个数

但其实这是必要条件而不是充分条件,这并不能保证最后可以为对角线

正确的应该是,如果每一个1占领一行和一列的话,可以放n个

这就联想到二分图匹配最大匹配为n了,因此可以用最大匹配来判断是否可以为对角线

这是这道题的第一部分

然后就是接下来怎么去构造的问题了

我自己的想法很简单,直接暴力一行一行的放,对于每一行,直接找到一个1然后通过交换达到对角线的位置

但是我又掉了一个坑,就是那些多余的点,也就是没有被匹配的点是不能算入的,不然会影响结果,这些点不能放到对角线哪里,只有匹配的点才能转移

你用那些多余点放到对角线的时候,就默认它是匹配的点了,但实际上它不是匹配的点

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 110;
int vis[N], link[N], a[N][N], n;
vector<int> g[N];

void Swap(int x, int y)
{
    _for(i, 1, n)
        swap(a[i][x], a[i][y]);
}

bool dfs(int u)
{
    for(int v: g[u])
    {
        if(vis[v]) continue;
        vis[v] = 1;
        if(!link[v] || dfs(link[v]))
        {
            link[v] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    while(~scanf("%d", &n))
    {
        memset(link, 0, sizeof(link));
        _for(i, 1, n) g[i].clear();

        _for(i, 1, n)
            _for(j, 1, n)
            {
                scanf("%d", &a[i][j]);
                if(a[i][j]) g[i].push_back(j);
            }

        int res = 0;
        _for(i, 1, n)
        {
            memset(vis, 0, sizeof(vis));
            if(dfs(i)) res++;
        }

        if(res < n)
        {
            puts("-1");
            continue;
        }

        memset(a, 0, sizeof(a));
        _for(i, 1, n) a[link[i]][i] = 1;
        vector<pair<int, int> > ans;
        _for(i, 1, n)
        {
            int p;
            _for(j, i, n)
                if(a[i][j])
                {
                    p = j;
                    break;
                }
            if(p == i) continue;
            Swap(i, p);
            ans.push_back(make_pair(i, p));
        }

        printf("%d\n", ans.size());
        for(auto x: ans) printf("C %d %d\n", x.first, x.second);
    }

    return 0;
}

当然有更简洁的方法,就是直接在匹配数组上操作,而不是在输入的矩阵上操作,这样也避免了操作不匹配的点

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 110;
int vis[N], link[N], choose[N], n;
vector<int> g[N];

bool dfs(int u)
{
    for(int v: g[u])
    {
        if(vis[v]) continue;
        vis[v] = 1;
        if(!link[v] || dfs(link[v]))
        {
            link[v] = u;
            choose[u] = v;
            return true;
        }
    }
    return false;
}

int main()
{
    while(~scanf("%d", &n))
    {
        memset(link, 0, sizeof(link));
        memset(choose, 0, sizeof(choose));
        _for(i, 1, n) g[i].clear();

        _for(i, 1, n)
            _for(j, 1, n)
            {
                int x; scanf("%d", &x);
                if(x) g[i].push_back(j);
            }

        int res = 0;
        _for(i, 1, n)
        {
            memset(vis, 0, sizeof(vis));
            if(dfs(i)) res++;
        }

        if(res < n)
        {
            puts("-1");
            continue;
        }

        vector<pair<int, int> > ans;
        _for(i, 1, n)
            if(link[i] != i)
            {
                ans.push_back(make_pair(i, link[i]));
                link[choose[i]] = link[i];
                choose[link[i]] = choose[i];
                link[i] = i; choose[i] = i;
            }

        printf("%d\n", ans.size());
        for(auto x: ans) printf("R %d %d\n", x.first, x.second);
    }

    return 0;
}

周四 5.27(匹配)

HDU 2389(HK算法)

写了匈牙利T了

匈牙利时间复杂度是O(nm)

而HK算法复杂度是O(sqrt(n) * m)

自己还没有完全理解,理解了百分之80吧

不过也只是模板,如果要用直接抄纸质模板

我的写法是左右编号存在了一起

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 3000 + 10;
struct node
{
    int x, y;
}a[N], b[N];
int v[N], dep[N << 1], match[N << 1], n, m, t; //我这里是左右两部分点都用一个数组,这样写简洁一些
vector<int> g[N];

bool bfs()
{
    bool res = false;
    memset(dep, 0, sizeof(dep));
    queue<int> q;
    _for(i, 1, n)
        if(!match[i]) //把未匹配的点加入
            q.push(i);

    while(!q.empty())
    {
        int u = q.front(); q.pop();
        for(int v: g[u])
        {
            if(dep[v]) continue;
            dep[v] = dep[u] + 1;
            if(!match[v]) res = true; //只要找到增广路就返回true
            else
            {
                dep[match[v]] = dep[v] + 1;
                q.push(match[v]);
            }
        }
    }
    return res;
}

bool dfs(int u)
{
    for(int v: g[u])
    {
        if(dep[v] != dep[u] + 1) continue; //按照增广路dfs
        dep[v] = 0;
        if(!match[v] || dfs(match[v]))
        {
            match[v] = u; match[u] = v;
            return true;
        }
    }
    return false;
}

bool check(int i, int j)
{
    return (a[i].x - b[j].x) * (a[i].x - b[j].x) + (a[i].y - b[j].y) * (a[i].y - b[j].y) <= t * t * v[i] * v[i];
}

int main()
{
    int T, kase = 0;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d", &t, &n);
        _for(i, 1, n) scanf("%d%d%d", &a[i].x, &a[i].y, &v[i]);
        scanf("%d", &m);
        _for(i, 1, m) scanf("%d%d", &b[i].x, &b[i].y);

        _for(i, 1, n) g[i].clear();
        _for(i, 1, n)
            _for(j, 1, m)
                if(check(i, j))
                    g[i].push_back(j + n); //右编号+n


        int ans = 0;
        memset(match, 0, sizeof(match));
        while(bfs()) //预处理出最短增广路
        {
            _for(i, 1, n)
                if(!match[i] && dfs(i)) //未匹配的去寻找增广路
                    ans++;
        }
        printf("Scenario #%d:\n%d\n\n", ++kase, ans);
    }

    return 0;
}

hdu 4185(建图)

这题我在建图这里卡了一下

之前是一个点看作一条边

这道题不一样,是一个点就是一个点,然后题目说的1x2的矩形看作一条边

边和点的定义不同

其实有提示了,题目问最多有多少个矩形

联系最大匹配,匹配的是边

所以矩形就是边

做的时候都加倍,点加倍,边加倍,然后答案除以2就好

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a) ; i < (b); i++)
#define _for(i, a, b) for(int i = (a) ; i <= (b); i++)
using namespace std;

const int N = 610;
int match[N * N], vis[N * N], id[N][N], n, cnt;
vector<int> g[N];
char s[N][N];

bool dfs(int u)
{
    for(int v: g[u])
    {
        if(vis[v]) continue;
        vis[v] = 1;
        if(!match[v] || dfs(match[v]))
        {
            match[v] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    int T, kase = 0;
    scanf("%d", &T);
    while(T--)
    {
       cnt = 0;
       memset(match, 0, sizeof(match));
       memset(s, 0, sizeof(s));

       scanf("%d", &n);
       _for(i, 1, n)
       {
           scanf("%s", s[i] + 1);
           _for(j, 1, n)
               if(s[i][j] == '#')
                    id[i][j] = ++cnt;
       }

       _for(i, 1, cnt) g[i].clear();
       _for(i, 1, n)
            _for(j, 1, n)
            {
                if(s[i][j] == '#' && s[i][j + 1] == '#')
                {
                    g[id[i][j]].push_back(id[i][j + 1]);
                    g[id[i][j + 1]].push_back(id[i][j]);
                }
                if(s[i][j] == '#' && s[i + 1][j] == '#')
                {
                    g[id[i][j]].push_back(id[i + 1][j]);
                    g[id[i + 1][j]].push_back(id[i][j]);
                }
            }

       int ans = 0;
       _for(i, 1, cnt)
       {
           memset(vis, 0, sizeof(vis));
           ans += dfs(i);
       }
       printf("Case %d: %d\n", ++kase, ans / 2);
    }

	return 0;
}

poj 3020(最小路径覆盖)

这道题和上一道题非常像,只是变成了覆盖所有最少需要多少矩形

从最小可以想到最小点覆盖,我按照这个思路发现很复杂

其实最小也有最小路径覆盖,也刚好是这道题,最少的矩形覆盖

最小路径覆盖 = 总节点数 - 最大匹配

所以求出最小路径覆盖,最后把答案除以2就行了,因为这个图是翻倍的

#include <cstdio>
#include <vector>
#include <cstring>
#define rep(i, a, b) for(int i = (a) ; i < (b); i++)
#define _for(i, a, b) for(int i = (a) ; i <= (b); i++)
using namespace std;

const int N = 400;
int match[N], vis[N], id[N][N], n, m, cnt;
vector<int> g[N];
char s[N][N];

bool dfs(int u)
{
    rep(i, 0, g[u].size())
    {
        int v = g[u][i];
        if(vis[v]) continue;
        vis[v] = 1;
        if(!match[v] || dfs(match[v]))
        {
            match[v] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    int T, kase = 0;
    scanf("%d", &T);
    while(T--)
    {
       cnt = 0;
       memset(match, 0, sizeof(match));
       memset(s, 0, sizeof(s));

       scanf("%d%d", &n, &m);
       _for(i, 1, n)
       {
           scanf("%s", s[i] + 1);
           _for(j, 1, m)
               if(s[i][j] == '*')
                    id[i][j] = ++cnt;
       }

       _for(i, 1, cnt) g[i].clear();
       _for(i, 1, n)
            _for(j, 1, m)
            {
                if(s[i][j] == '*' && s[i][j + 1] == '*')
                {
                    g[id[i][j]].push_back(id[i][j + 1]);
                    g[id[i][j + 1]].push_back(id[i][j]);
                }
                if(s[i][j] == '*' && s[i + 1][j] == '*')
                {
                    g[id[i][j]].push_back(id[i + 1][j]);
                    g[id[i + 1][j]].push_back(id[i][j]);
                }
            }

       int ans = 0;
       _for(i, 1, cnt)
       {
           memset(vis, 0, sizeof(vis));
           ans += dfs(i);
       }
       printf("%d\n", (cnt * 2 - ans) / 2);
    }

	return 0;
}

周五 5.28 (匹配)

I - Strategic Game(最小点覆盖)

点看作点,边看作边,建立二分图

最小点覆盖 = 最大匹配就好

点加倍,边加倍

结果除以2

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2000;
int match[N], vis[N], n;
vector<int> g[N];

bool dfs(int u)
{
    for(int v: g[u])
    {
        if(vis[v]) continue;
        vis[v] = 1;
        if(!match[v] || dfs(match[v]))
        {
            match[v] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    while(~scanf("%d", &n))
    {
        _for(i, 1, n) g[i].clear();
        memset(match, 0, sizeof(match));

        _for(i, 1, n)
        {
            int u, m;
            scanf("%d:(%d)", &u, &m);
            u++;
            while(m--)
            {
                int v; scanf("%d", &v);
                v++;
                g[u].push_back(v);
                g[v].push_back(u);
            }
        }

        int ans = 0;
        _for(i, 1, n)
        {
            memset(vis, 0, sizeof(vis));
            ans += dfs(i);
        }
        printf("%d\n", ans / 2);
    }

    return 0;
}

poj 1975(传递闭包)

刷到一道题有关于传递闭包的,搞一波

其实就是用有向图中用floyed判断出从这个点是否可以到达那个点

这道题就求个传递闭包

然后看有多少比其重和比其轻就好了

#include <cstdio>
#include <cstring>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 110;
int g[N][N], n, m;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        memset(g, 0, sizeof(g));
        scanf("%d%d", &n, &m);
        while(m--)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u][v] = 1;
        }

        _for(k, 1, n)
            _for(i, 1, n)
                _for(j, 1, n)
                    g[i][j] |= g[i][k] && g[k][j];

        int ans = 0;
        _for(i, 1, n)
        {
            int cnt1 = 0, cnt2 = 0;
            _for(j, 1, n) cnt1 += g[i][j], cnt2 += g[j][i];
            if(cnt1 > n / 2 || cnt2 > n / 2) ans++;
        }
        printf("%d\n", ans);
    }

	return 0;
}

poj 3660(传递闭包)

和上一题很像

只有知道了它和其他所有牛的关系才可确定其排名

#include <cstdio>
#include <cstring>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 110;
int g[N][N], n, m;

int main()
{
    scanf("%d%d", &n, &m);
    while(m--)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u][v] = 1;
    }

    _for(k, 1, n)
        _for(i, 1, n)
            _for(j, 1, n)
                g[i][j] |= g[i][k] && g[k][j];

    int ans = 0;
    _for(i, 1, n)
    {
        int cnt = 0;
        _for(j, 1, n) cnt += g[i][j] + g[j][i];
        ans += cnt == n - 1;
    }
    printf("%d\n", ans);

	return 0;
}

hdu 1151(结论)

这道题每想出来

原来是有一个结论……

有向图的最小路径覆盖 = 节点数 - 最大匹配数

这里的最小路径覆盖指的是最少的路径覆盖整个图,每个点只在一条路径上

 

在网上有看到一个证明,很巧妙

首先没有边的时候答案是n条路径

如果u和v有一条边,那么匹配数+ 1需要的路径数 - 1

最多能匹配多少,路径就减去多少

很秀

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 200;
int match[N], vis[N], n, m;
vector<int> g[N];

bool dfs(int u)
{
    for(int v: g[u])
    {
        if(vis[v]) continue;
        vis[v] = 1;
        if(!match[v] || dfs(match[v]))
        {
            match[v] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        memset(match, 0, sizeof(match));
        scanf("%d%d", &n, &m);
        _for(i, 1, n) g[i].clear();
        while(m--)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u].push_back(v);
        }

        int ans = 0;
        _for(i, 1, n)
        {
            memset(vis, 0, sizeof(vis));
            ans += dfs(i);
        }
        printf("%d\n", n - ans);
    }

	return 0;
}

poj 2594(传递闭包+ 结论)

这道题和上一道题的区别就是路径可以重复覆盖一个点

怎么解决这个问题呢

用传递闭包

不仅是直接连边的可以连边

可以到达的也连边,这样就可以重复

#include <cstdio>
#include <cstring>
#include <vector>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 510;
int g[N][N], match[N], vis[N], n, m;

bool dfs(int u)
{
    _for(v, 1, n)
    {
        if(vis[v] || !g[u][v]) continue;
        vis[v] = 1;
        if(!match[v] || dfs(match[v]))
        {
            match[v] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    while(scanf("%d%d", &n, &m) && n)
    {
        memset(match, 0, sizeof(match));
        memset(g, 0, sizeof(g));

        while(m--)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u][v] = 1;
        }

        _for(k, 1, n)
            _for(i, 1, n)
                _for(j, 1, n)
                    g[i][j] |= g[i][k] && g[k][j];

        int ans = 0;
        _for(i, 1, n)
        {
            memset(vis, 0, sizeof(vis));
            ans += dfs(i);
        }
        printf("%d\n", n - ans);
    }

	return 0;
}

L - Cat VS Dog(最大独立集)

首先如果每个人讨厌的都移除的话,就有一些会冲突

而最大独立集就可以处理这样的有冲突的情况下最多取多少的问题

所以就以人为节点,把矛盾连边

然后求最大独立集就好了。结果再除以2

每次左右是相同类型的节点的话,边要连两条,最后结果除以2

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 510;
int match[N], vis[N], n, m, p;
vector<int> g[N];
string a[N], b[N];

bool dfs(int u)
{
    for(int v: g[u])
    {
        if(vis[v]) continue;
        vis[v] = 1;
        if(!match[v] || dfs(match[v]))
        {
            match[v] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    while(~scanf("%d%d%d", &n, &m, &p))
    {
        memset(match, 0, sizeof(match));
        _for(i, 1, p) cin >> a[i] >> b[i], g[i].clear();

        _for(i, 1, p)
            _for(j, i + 1, p)
                if(a[i] == b[j] || a[j] == b[i])
                {
                    g[i].push_back(j);
                    g[j].push_back(i);
                }

        int ans = 0;
        _for(i, 1, p)
        {
            memset(vis, 0, sizeof(vis));
            ans += dfs(i);
        }
        printf("%d\n", (p * 2 - ans) / 2);
    }

	return 0;
}

M - Jamie's Contact Groups(二分图多重匹配)

一读完题就知道二分答案

但是一个组可以多个人,就不符合之前最大匹配的定义

然后就卡了很久

看了题解发现是二分图多重匹配,尼玛不知道这个知识点

有时候卡题是因为有些知识点不知道,就不要死卡在那里了

 

二分图多重匹配就是右侧的点可以连多条边,但是有限制的连边个数

思路和匈牙利是一样的,也是每次去寻找增广路,只是可以连多次

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int vis[N], n, m;
vector<int> g[N], match[N];

bool dfs(int u, int key)
{
    rep(i, 0, g[u].size())
    {
        int v = g[u][i];
        if(vis[v]) continue;
        vis[v] = 1;

        if(match[v].size() < key) 
        { 
            match[v].push_back(u); //如果还可以匹配就加入新的边
            return true;
        }
        else
        {
            rep(j, 0, match[v].size())
                if(dfs(match[v][j], key)) //类似最大匹配
                {
                    match[v][j] = u;  //找到了就替换
                    return true;
                }
        }
    }
    return false;
}

bool check(int key)
{
    _for(i, 1, m) match[i].clear();
    _for(i, 1, n)
    {
        memset(vis, 0, sizeof(vis));
        if(!dfs(i, key)) return false;
    }
    return true;
}

int main()
{
    while(scanf("%d%d", &n, &m) && n)
    {
        _for(i, 1, n) g[i].clear();
        _for(i, 1, n)
        {
            int x;
            string name;

            cin >> name;
            while(1)
            {
                cin >> x;
                g[i].push_back(++x);
                if(getchar() == '\n') break;
            }
        }

        int l = 0, r = n;
        while(l + 1 < r)
        {
            int m = l + r >> 1;
            if(check(m)) r = m;
            else l = m;
        }
        printf("%d\n", r);
    }

	return 0;
}

O - Steady Cow Assignment(二分图多重匹配)

二分图多重匹配不是求最优,而是验证是否可以匹配

所以经常用来验证答案,比如二分答案,枚举答案

这道题b很小只有20,不用二分答案直接枚举也可以

判断每一个答案的时候,知道了差值,那就直接枚举区间就好了

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int g[N][25], vis[N], lim[N], n, b;
vector<int> match[N];

bool dfs(int u, int l, int r)
{
    _for(i, l, r)
    {
        int v = g[u][i];
        if(vis[v]) continue;
        vis[v] = 1;
        if(match[v].size() < lim[v])
        {
            match[v].push_back(u);
            return true;
        }
        else
        {
            rep(j, 0, match[v].size())
                if(dfs(match[v][j], l, r))
                {
                    match[v][j] = u;
                    return true;
                }
        }
    }
    return false;
}

bool pd(int l, int r)
{
    _for(i, 1, b) match[i].clear();
    _for(i, 1, n)
    {
        memset(vis, 0, sizeof(vis));
        if(!dfs(i, l, r)) return false;
    }
    return true;
}

bool check(int len)
{
    _for(l, 1, b)
    {
        int r = l + len - 1;
        if(r > b) break;
        if(pd(l, r)) return true;
    }
    return false;
}

int main()
{
    scanf("%d%d", &n, &b);
    _for(i, 1, n)
        _for(j, 1, b)
            scanf("%d", &g[i][j]);
    _for(i, 1, b) scanf("%d", &lim[i]);

    int l = 0, r = b;
    while(l + 1 < r)
    {
        int m = l + r >> 1;
        if(check(m)) r = m;
        else l = m;
    }
    printf("%d\n", r);

	return 0;
}

周六 5.29

N - Optimal Milking(二分图多重匹配)

二分答案+多重匹配

用Floyed预处理一下

注意0是代表无穷大(题目没理解情况卡了很久)

#include <cstdio>
#include <cstring>
#include <vector>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 250;
int g[N][N], vis[N], k, c, m;
vector<int> match[N];

bool dfs(int u, int key)
{
    _for(v, 1, k)
    {
        if(vis[v] || g[u][v] > key) continue;
        vis[v] = 1;

        if(match[v].size() < m)
        {
            match[v].push_back(u);
            return true;
        }
        else
        {
            rep(j, 0, match[v].size())
                if(dfs(match[v][j], key))
                {
                    match[v][j] = u;
                    return true;
                }
        }
    }
    return false;
}

bool check(int key)
{
    _for(i, 1, k) match[i].clear();
    _for(i, k + 1, k + c)
    {
        memset(vis, 0, sizeof(vis));
        if(!dfs(i, key)) return false;
    }
    return true;
}

int main()
{
    scanf("%d%d%d", &k, &c, &m);
    _for(i, 1, k + c)
        _for(j, 1, k + c)
        {
            scanf("%d", &g[i][j]);
            if(i != j && !g[i][j]) g[i][j] = 1e9;
        }

    _for(K, 1, k + c)
        _for(i, 1, k + c)
            _for(j, 1, k + c)
                g[i][j] = min(g[i][j], g[i][K] + g[K][j]);

    int l = -1, r = 50000;
    while(l + 1 < r)
    {
        int m = l + r >> 1;
        if(check(m)) r = m;
        else l = m;
    }
    printf("%d\n", r);

	return 0;
}

P - 奔小康赚大钱(二分图最优匹配)

最优匹配就是指权值最大的完备匹配

完备匹配指x中每一个顶点都匹配成功或者y中每一个顶点都匹配成功

解法是KM算法

其实就是匈牙利 + 贪心

一开始先是没有边

然后把x中每个点的最大的边权的边加入图中

然后对于每一个x的点去匹配,用匈牙利算法

因为这时边是很少的,所以会匹配不成功

那怎么办呢,那接下来把点的第二大的边加入图中,再去匹配

总之就是每次匹配不成功就加入一条目前最优的边进去,然后再匹配

这个加边的过程挺麻烦,KM算法用了一个非常非常巧妙的方法很方便地实现了这个过程

给左右的点赋一个权值lx ly

当lx[i] + ly[j] == g[i][j]时这条边才存在

初始化时左边的lx赋值为这个点的连的边的最大权值,右边为0

根据边的存在条件,这个时候就只有每个点的最大权值边

然后去匹配,关键是这个加边操作

每次要加入次大的边,也就是g[i][j]没那么大的边

也就是差值就是lx[i] + ly[j] - g[i][j] 显然这个差值越小,那么这个边的权值最大

所以就可以在dfs的过程中维护这小权值

得出这个权值后,对于所有访问过的点,左边lx[i] -= d 右边ly[i] += d

这样的话,原来符合lx[i] + ly[j] == g[i][j]的依然符合,不同的是原来lx[i] + ly[j] > g[i][j]的

这时lx[i] -= d 而ly[j]是不变的,因为这条边根本不存在(看边的存在条件),所以不会去访问

lx[i]减去了一个d使得次小边lx[i] + ly[j] == g[i][j] 刚好符合,也就是实现了加入了一条次小边

多加了一条边就更容易匹配成功了,就这样一直贪心地加入新的边,一直这样做下去答案就是最优匹配

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 310;
int match[N], g[N][N], lx[N], ly[N], n;
int visx[N], visy[N], d;

bool dfs(int u)
{
    visx[u] = 1;
    _for(v, 1, n)
    {
        if(visy[v]) continue;
        int t = lx[u] + ly[v] - g[u][v];
        if(t == 0)
        {
            visy[v] = 1; //注意这里是这条边存在才visy[v] = 1
            if(!match[v] || dfs(match[v]))
            {
                match[v] = u;
                return true;
            }
        }
        else if(t > 0) d = min(d, t); //寻找权值次小的边,注意要t > 0
    }
    return false;
}

int main()
{
    while(~scanf("%d", &n))
    {
        memset(match, 0, sizeof match);
        memset(lx, 0, sizeof lx);
        memset(ly, 0, sizeof ly);

        _for(i, 1, n)
            _for(j, 1, n)
            {
                scanf("%d", &g[i][j]);
                lx[i] = max(lx[i], g[i][j]); //初始化为最大边权
            }

        _for(i, 1, n)
        {
            while(1) //一直添加新的边,直到找到为止
            {
                memset(visx, 0, sizeof visx);
                memset(visy, 0, sizeof visy);
                d = 1e9;
                if(dfs(i)) break;
                _for(j, 1, n)
                {
                    if(visx[j]) lx[j] -= d; //把所有访问过的边都改变
                    if(visy[j]) ly[j] += d; //相当于添加新边
                }
            }
        }

        int ans = 0;
        _for(i, 1, n)
            ans += g[match[i]][i];
        printf("%d\n", ans);
    }

	return 0;
}

Q - Tour(二分图最优匹配)

把这个有向图转化成二分图

题目说环不能相交,其实就意味着不能有两条以上的有向边指向一个节点

这不就刚好是匹配吗

而且说每个点经过一次,这就刚好是完备匹配了

但是是求最小权值,那就取负值然后跑最优匹配就好了

用领接矩阵存图,不存在的边看作负无穷

注意有个大坑就是有重边,我因为这个wa了几发

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 210;
int lx[N], ly[N], match[N], visx[N], visy[N];
int g[N][N], d, n, m;

bool dfs(int u)
{
    visx[u] = 1;
    _for(v, 1, n)
    {
        if(visy[v]) continue;
        int t = lx[u] + ly[v] - g[u][v];
        if(!t)
        {
            visy[v] = 1;
            if(!match[v] || dfs(match[v]))
            {
                match[v] = u;
                return true;
            }
        }
        else d = min(d, t); //这里可以不写t > 0 因为t不可能小于0
    }
    return false;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        memset(match, 0, sizeof match);
        memset(lx, 0, sizeof lx);
        memset(ly, 0, sizeof ly);
        memset(g, 128, sizeof g);

        scanf("%d%d", &n, &m);
        while(m--)
        {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            g[u][v] = max(g[u][v], -w);
        }

        _for(i, 1, n)
        {
            lx[i] = g[i][1];
            _for(j, 2, n)
                lx[i] = max(lx[i], g[i][j]);
        }

        _for(i, 1, n)
        {
            while(1)
            {
                memset(visx, 0, sizeof visx);
                memset(visy, 0, sizeof visy);
                d = 1e9;
                if(dfs(i)) break;
                _for(j, 1, n)
                {
                    if(visx[j]) lx[j] -= d;
                    if(visy[j]) ly[j] += d;
                }
            }
        }

        int ans = 0;
        _for(i, 1, n)
            ans += g[match[i]][i];
        printf("%d\n", -ans);
    }

	return 0;
}

R - Work Scheduling(一般图最大匹配)

又是新算法

而且这个算法还挺复杂

我直接抄了模板,比赛时带模板就好了

看了这位同学的代码

https://blog.csdn.net/qq_49494204/article/details/115705600

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 300;
int g[N][N], match[N], pre[N], fa[N], vis[N], inblossom[N], n;
deque<int> q;

int lca(int u, int v)
{
    bool inpath[N] = {0};
    while(1)
    {
    	u = fa[u];
		inpath[u] = true;
		if(match[u] == -1) break;
		u = pre[match[u]];	
	}
	while(1)
	{
		v = fa[v];
		if(inpath[v]) return v;
		v = pre[match[v]];
	}
    return v;
}

void reset(int u, int anc)
{
	while(u != anc)
	{
		int v = match[u];
		inblossom[fa[u]] = 1;
		inblossom[fa[v]] = 1;
		v = pre[v];
		if(fa[v] != anc) pre[v] = match[u];
		u = v;
	}
}

void contract(int u, int v)
{
	int anc = lca(u, v);
	memset(inblossom, 0, sizeof inblossom);
	reset(u, anc); reset(v, anc);
	if(fa[u] != anc) pre[u] = v;
	if(fa[v] != anc) pre[v] = u;
	_for(i, 1, n)
		if(inblossom[fa[i]])
		{
			fa[i] = anc;
			if(!vis[i])
			{
				q.push_back(i);
				vis[i] = 1;
			}
		}
}

bool dfs(int s)
{
	_for(i, 0, n) pre[i] = -1, vis[i] = 0, fa[i] = i;
	q.clear();
	q.push_back(s); vis[s] = 1;
	while(!q.empty())
	{
		int u = q.front(); q.pop_front();
		_for(v, 1, n)
			if(g[u][v] && fa[v] != fa[u] && match[u] != v)
			{
				if(v == s || match[v] != -1 && pre[match[v]] != -1) contract(u, v);
				else if(pre[v] == -1)
				{
					pre[v] = u;
					if(match[v] != -1) q.push_back(match[v]), vis[match[v]] = 1;
					else
					{
						u = v;
						while(u != -1)
						{
							v = pre[u];
							int w = match[v];
							match[u] = v;
							match[v] = u;
							u = w;
						}
						return true;
					}
				}
			}
	}
	return false;
}

int main()
{
    int u, v;
    memset(match, -1, sizeof match);
    scanf("%d", &n);
	while(~scanf("%d%d", &u, &v)) g[u][v] = g[v][u] = 1;
	    
	int ans = 0;
	_for(i, 1, n)
	    if(match[i] == -1)
	    	ans += dfs(i);
	    
	printf("%d\n", ans * 2);
	_for(i, 1, n)
	    if(match[i] != -1 && match[i] < i)
	        printf("%d %d\n", match[i], i);

	return 0;
}

周日 5.30

S - Boke and Tsukkomi(一般图最大匹配)

n个点,给你一些边。问在形成最大匹配的情况下,哪些边是多余的

(1)删除边的时候,根据题意,是相当于删除这两个点,所以与这两个点相连的所有边都要删除

(2)如果一条边没了,不能实现最大匹配,那其就不是多余的,否则是多余的

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 50;
int g[N][N], match[N], pre[N], fa[N], vis[N], inblossom[N], n, m;
deque<int> q;

int lca(int u, int v)
{
    bool inpath[N] = {0};
    while(1)
    {
    	u = fa[u];
		inpath[u] = true;
		if(match[u] == -1) break;
		u = pre[match[u]];
	}
	while(1)
	{
		v = fa[v];
		if(inpath[v]) return v;
		v = pre[match[v]];
	}
    return v;
}

void reset(int u, int anc)
{
	while(u != anc)
	{
		int v = match[u];
		inblossom[fa[u]] = 1;
		inblossom[fa[v]] = 1;
		v = pre[v];
		if(fa[v] != anc) pre[v] = match[u];
		u = v;
	}
}

void contract(int u, int v)
{
	int anc = lca(u, v);
	memset(inblossom, 0, sizeof inblossom);
	reset(u, anc); reset(v, anc);
	if(fa[u] != anc) pre[u] = v;
	if(fa[v] != anc) pre[v] = u;
	_for(i, 1, n)
		if(inblossom[fa[i]])
		{
			fa[i] = anc;
			if(!vis[i])
			{
				q.push_back(i);
				vis[i] = 1;
			}
		}
}

bool dfs(int s)
{
	_for(i, 0, n) pre[i] = -1, vis[i] = 0, fa[i] = i;
	q.clear();
	q.push_back(s); vis[s] = 1;
	while(!q.empty())
	{
		int u = q.front(); q.pop_front();
		_for(v, 1, n)
			if(g[u][v] && fa[v] != fa[u] && match[u] != v)
			{
				if(v == s || match[v] != -1 && pre[match[v]] != -1) contract(u, v);
				else if(pre[v] == -1)
				{
					pre[v] = u;
					if(match[v] != -1) q.push_back(match[v]), vis[match[v]] = 1;
					else
					{
						u = v;
						while(u != -1)
						{
							v = pre[u];
							int w = match[v];
							match[u] = v;
							match[v] = u;
							u = w;
						}
						return true;
					}
				}
			}
	}
	return false;
}

int solve()
{
    memset(match, -1, sizeof match);
    int res = 0;
        _for(i, 1, n)
            if(match[i] == -1)
                res += dfs(i);
    return res;
}

int main()
{
    while(~scanf("%d%d", &n, &m))
    {
        vector<pair<int, int> > Edge;
        vector<int> ans;
        memset(g, 0, sizeof g);

        _for(i, 1, m)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u][v] = g[v][u] = 1;
            Edge.push_back(make_pair(u, v));
        }

        int mx = solve();
        rep(i, 0, Edge.size())
        {
            int u = Edge[i].first, v = Edge[i].second;
            memset(g, 0, sizeof g);
            rep(j, 0, Edge.size())
            {
                int x = Edge[j].first, y = Edge[j].second;
                if(x == u || y == u || x == v || y == v) continue;
                g[x][y] = g[y][x] = 1;
            }
            if(solve() + 1 != mx) ans.push_back(i + 1);
        }

        printf("%d\n", ans.size());
        rep(i, 0, ans.size())
        {
            printf("%d", ans[i]);
            if(i != ans.size() - 1) putchar(' ');
        }
        puts("");
    }

	return 0;
}

CodeForces - 1501C(时间复杂度)

这道题出得出其不意

比赛的时候只想到了n方的算法,看数据范围肯定超时

结果看题解后发现,实际上数值大小只在1到5e6

所以最多跑到5e6,是不会跑到n方那么大的,到5e6之前肯定已经找到了

太秀了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
const int M = 5e6 + 10;
pair<int, int> k[M];
int vis[M], a[N], n;

int main()
{
    scanf("%d", &n);
    _for(i, 1, n) scanf("%d", &a[i]);

    _for(i, 1, n)
        _for(j, i + 1, n)
        {
            int now = a[i] + a[j];
            if(vis[now] && i != k[now].first && j != k[now].second && i != k[now].second && j != k[now].first)
            {
                puts("YES");
                printf("%d %d %d %d\n", i, j, k[now].first, k[now].second);
                return 0;
            }
            vis[now] = 1;
            k[now] = make_pair(i, j);
        }
    puts("NO");

	return 0;
}

CodeForces - 1328D(思维)

比赛时想到了正解的思路,然而有个小地方想错了没A掉

首先可以121212,发现只要是偶数就可以这样放。

如果是奇数的话,这样放就必然有一个11或者22 这时要求类型相同

所以可以遍历一遍,找到相邻类型相同的设置为1,然后往两侧121212这样就好了

如果找不到就需要3,这时在结尾的时候放一个3就行了

我比赛时脑抽了搞成放123123123123,这样时有可能1231,首尾都为1要求类型相同,就是错误的

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
int a[N], ans[N], n;

void change(int &x)
{
    if(x == 2) x = 1;
    else x = 2;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &a[i]);
        a[n + 1] = a[1]; a[0] = a[n];

        bool ok = false;
        _for(i, 1, n)
            if(a[i - 1] != a[i])
            {
                ok = true;
                break;
            }

        if(!ok)
        {
            printf("1\n");
            _for(i, 1, n) printf("1 "); puts("");
            continue;
        }

        int k = 2;
        if(n % 2 == 0) _for(i, 1, n) ans[i] = i % 2 + 1;
        else
        {
            int p;
            ok = false;
            _for(i, 1, n)
                if(a[i] == a[i - 1])
                {
                    p = i;
                    ok = true;
                    break;
                }

            if(!ok)
            {
                k++;
                _for(i, 1, n - 1) ans[i] = i % 2 + 1;
                ans[n] = 3;
            }
            else
            {
                int t;
                if(p == 1)
                {
                    ans[1] = ans[n] = 1;
                    t = 1;
                    _for(i, 2, n - 1)
                    {
                        change(t);
                        ans[i] = t;
                    }
                }
                else
                {
                    ans[p] = ans[p - 1] = 1;
                    t = 1;
                    _for(i, p + 1, n)
                    {
                        change(t);
                        ans[i] = t;
                    }

                    t = 1;
                    for(int i = p - 2; i >= 1; i--)
                    {
                        change(t);
                        ans[i] = t;
                    }
                }
            }
        }

        printf("%d\n", k);
        _for(i, 1, n) printf("%d ", ans[i]);
        puts("");
    }

	return 0;
}

Gym - 102028D(三角函数)

这题考试时静下心来做应该可以想出,可惜太着急

可以发现一条边长度固定,形成一个圆,推一推公式就好

注意角度和弧度的转换,求角度

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const double pi = acos(-1.0);

int dcmp(double x)
{
    if(fabs(x) < 1e-8) return 0;
    return x > 0 ? 1 : -1;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        double a, b, r, d;
        scanf("%lf%lf%lf%lf", &a, &b, &r, &d);
        d = (d * pi / 180); //一开始就把角度转成弧度 
        double thi = atan(b / (a + r)); //用atan acos等等
        double maxr = sqrt(pow(a + r, 2) + pow(b, 2));

        if(dcmp(d - thi) >= 0)
        {
            printf("%.12f\n", maxr - r);
            continue;
        }

        double t = thi - d;
        double L = maxr * sqrt(2 * (1 - cos(t)));
        double tt = (pi / 2) - (pi - t) / 2;
        printf("%.12f\n", maxr - r - L * sin(tt));
    }

	return 0;
}

Parsa's Humongous Tree(观察+ 树形dp)

这道题我没想出来

有一个结论,就是取端点一定时最优秀的,我想到这个

其实自己想的时候也可以猜一下取端点一定是最优的

如果知道这个那就很水了,直接树形dp一波就好了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
int l[N], r[N], n;
vector<int> g[N];
ll dp[N][2]; //0 l   1 r

void dfs(int u, int fa)
{
    dp[u][0] = dp[u][1] = 0;
    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs(v, u);
        dp[u][0] += max(dp[v][0] + abs(l[u] - l[v]), dp[v][1] + abs(l[u] - r[v]));
        dp[u][1] += max(dp[v][0] + abs(r[u] - l[v]), dp[v][1] + abs(r[u] - r[v]));
    }
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d%d", &l[i], &r[i]), g[i].clear();
        _for(i, 1, n - 1)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
        }

        dfs(1, 0);
        printf("%lld\n", max(dp[1][0], dp[1][1]));
    }

	return 0;
}

CodeForces - 1433D(并查集)

这就是一道大水题,也是我比赛中唯一没看的一道题……

以后个人赛中做到把所有的题目意思都理解好,并且有初步的思考

不然就会出现这种情况,漏掉大水题……

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5e3 + 10;
int a[N], f[N], n;

int Find(int x)
{
    if(f[x] == x)
        return x;
    return f[x] = Find(f[x]);
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &a[i]), f[i] = i;

        vector<pair<int, int> > ans;
        _for(i, 1, n)
            _for(j, 1, n)
                if(a[i]!= a[j] && Find(i) != Find(j))
                {
                    ans.push_back(make_pair(i, j));
                    f[Find(i)] = Find(j);
                }

        if(ans.size() != n - 1) puts("NO");
        else
        {
            puts("YES");
            for(auto x: ans) printf("%d %d\n", x.first, x.second);
        }
    }

	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值