JZOJ 5748 小Y增员操直播群

传送门
题面

思路

这道题太 fake 了,打个暴力都能过,丢分居然是因为没有判断重边和自环 QAQ,唉,我太弱啦!

显然我们可以把这些人分成一段一段的,然后分治处理,则最后的答案就是分治处理过程形成的树的最大深度 - 1。所以现在的关键是如何找到分割点。

什么都不会的我当然是选择暴力啦!暴力枚举分割点,然后暴力检查:判断后面部分是否正确向前面部分连了边,且后面部分的每个点向前面部分连边的数量恰好为 1 。可以通过对边排序后二分查找实现。

int divide(int l, int r)
{
    if (l == r)
        return 0;
    for (int i = l, to = l + ((r - l + 1) >> 1) - 1; i <= to; i++)
    {
        bool bOk = true;
        for (int j = i + 1, t = l; j <= r; j++, t = (t == i ? l : t + 1))
        {
            if (!(std::binary_search(G[j].begin(), G[j].end(), t) &&
                countRange(G[j], l, i) == 1))
            {
                bOk = false;
                break;
            }
        }
        if (bOk)
        {
            int a = divide(l, i);
            int b = divide(i + 1, r);
            if (a == -1 || b == -1)
                return -1;
            return 1 + std::max(a, b);
        }
    }
    return -1;
}

然后我就得到了 90 分!!!当然我就不会再管这道题了,最后下来告诉我,那十分是因为存在重边和自环。出题人十分良心因此只在前面的两个点里加了重边和自环。唉,我太弱了,写了一个复杂度错误特判错误下标错误的程序,没想到居然得到了 90 分,唉,我太弱啦!

正解

正解只提到了如何找分割点。怎么判断是否合法?不可能,这辈子都不会遇到写得详细的题解了,毕竟大家都是神仙,唉,我太弱啦!

嗯,没错,这就是判断合法的方法,太强啦!

现在考虑如何找到分割点。对于区间 [ l , r ] ,考虑 r 连向的在范围内的编号最小的那个点,设为 p r e ,那么显然这就是后半部分连向前半部分的一条边。那么显然, r ( p r e l ) 一定连向了 l ,而 r ( p r e l ) 1 自然就连向了前半部分编号最大的点了,这样我们就找到分割点了。特别地,若 r 连向的点使得 [ l , r ] 分成了相同大小的两块,那么 r 连向的这个点就是分割点。

然后怎么办呢?像暴力那样强行判断是否合法就可以了。

参考代码

(C++ 11)

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
typedef long long LL;
typedef unsigned long long ULL;
using std::cin;
using std::cout;
using std::endl;
typedef int INT_PUT;
INT_PUT readIn()
{
    INT_PUT a = 0; bool positive = true;
    char ch = getchar();
    while (!(ch == '-' || std::isdigit(ch))) ch = getchar();
    if (ch == '-') { positive = false; ch = getchar(); }
    while (std::isdigit(ch)) { a = a * 10 - (ch - '0'); ch = getchar(); }
    return positive ? -a : a;
}
void printOut(INT_PUT x)
{
    char buffer[20]; int length = 0;
    if (x < 0) putchar('-'); else x = -x;
    do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
    do putchar(buffer[--length]); while (length);
    putchar('\n');
}

const int maxn = int(1e5) + 5;
int n, m;

std::vector<std::vector<int> > G;
int countRange(const std::vector<int>& G, int l, int r)
{
    return std::upper_bound(G.begin(), G.end(), r) - std::lower_bound(G.begin(), G.end(), l);
}

bool check(int l, int r, int mid)
{
    for (int i = mid + 1, t = l; i <= r; i++, t = (t == mid ? l : t + 1))
    {
        if (!(std::binary_search(G[i].begin(), G[i].end(), t) &&
            countRange(G[i], l, mid) == 1))
        {
            return false;
        }
    }
    return true;
}
int divide(int l, int r)
{
    if (l == r)
        return 0;

    auto it = std::lower_bound(G[r].begin(), G[r].end(), l);
    if (it == G[r].end() || *it > r)
        return -1;
    int pre = r - (*it - l + 1);
    if (pre < l)
        return -1;
    if (pre - l + 1 == r - pre)
    {
        if (!check(l, r, pre))
            return -1;
        int a = divide(l, pre);
        int b = divide(pre + 1, r);
        if (a == -1 || b == -1)
            return -1;
        return 1 + std::max(a, b);
    }

    it = std::lower_bound(G[pre].begin(), G[pre].end(), l);
    if (it == G[pre].end() || *it > r || *it - l + 1 > r - *it)
        return -1;
    if (!check(l, r, *it))
        return -1;

    int a = divide(l, *it);
    int b = divide(*it + 1, r);
    if (a == -1 || b == -1)
        return -1;
    return 1 + std::max(a, b);
}

void run()
{
    int T = readIn();
    while (T--)
    {
        G.clear();
        n = readIn();
        m = readIn();
        G.resize(n);
        bool bOk = true;
        for (int i = 1; i <= m; i++)
        {
            int from = readIn();
            int to = readIn();
            if (from == to)
            {
                bOk = false;
            }
            if (from < to) std::swap(from, to);
            G[from].push_back(to);
        }
        if (!bOk)
        {
            printOut(-1);
            continue;
        }
        for (int i = 0; i < n; i++)
            std::sort(G[i].begin(), G[i].end());
        for (int i = 0; i < n; i++)
        {
            int size = G[i].size();
            int newsize = std::unique(G[i].begin(), G[i].end()) - G[i].begin();
            if (size != newsize)
            {
                bOk = false;
                break;
            }
        }
        if (!bOk)
        {
            printOut(-1);
            continue;
        }

        printOut(divide(0, n - 1));
    }
}

int main()
{
#ifndef LOCAL
    freopen("gymnastics.in", "r", stdin);
    freopen("gymnastics.out", "w", stdout);
#endif
    run();
    return 0;
}
时间复杂度

寻找分割点的时间复杂度显然是 O ( log n ) 的,这一步的总时间复杂度显然是 O ( n log n ) 的。现在我们考虑判断的时间复杂度。

考虑最坏情况,当然是分割点在很前面,需要花费 O ( n log n ) 的时间去判断后面部分的点与前面部分的连接情况。即使在最好情况也需要花费 O ( n 2 log n ) 的时间。难道这道题是道玄学题?

当然不(shì)。由于判断一个点是否满足要求相当于要检查一条边,而边数是 O ( m ) 的,且边不会被重复检查,因此时间复杂度是 O ( m log n ) 的。

猜你喜欢

转载自blog.csdn.net/lycheng1215/article/details/80496933