欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【JOISC 2020】Chameleon’s Love

程序员文章站 2024-03-19 08:51:22
...

题目

题目译自 JOISC 2020 Day2 T1「カメレオンの恋 / Chameleon’s Love」

注意:在 LibreOJ 上,由于语言限制,目前只支持以下语言的提交:

C++
C++ 11
C++ 17
C++ (NOI)
C++ 11 (NOI)
C++ 11 (Clang)
C++ 17 (Clang)
在 JOI 动物园中,有着 只变色龙,编号为 。其中,有 只变色龙的性别为 X,其余 只的性别为 Y。

每只变色龙都有一个原色。关于原色的可公开情报如下:

所有性别为 X 的变色龙的原色不同。
对于每只性别为 X 的变色龙,都存在唯一的原色与其相同的变色龙,且性别为 Y。
现在,JOI 动物园迎来了恋爱的季节。每只变色龙都爱上了另一只变色龙。关于恋爱对象的可公开情报如下:

每只变色龙都很专一于唯一一只异性的变色龙。
一只变色龙和它的恋爱对象的原色不同。
不存在两只变色龙同时追求另一只变色龙。
你可以召集一部分变色龙来组织一场会议。对于一只参加会议的变色龙 ,令 为它的恋爱对象。 的肤色由以下方式决定:

如果 参加了这场会议,则 的肤色为 的原色。
如果 没参加这场会议,则 的肤色为 的原色。
一只变色龙的肤色可以在不同的会议间发生改变。对于你组织的一场会议,你可以得到场上所有变色龙的肤色的种类数。

由于变色龙也会感到厌烦,所以你最多只能组织 场会议。同时你需要根据你得到的信息,确定有哪些变色龙的原色相同。
请你写一个程序在组织不超过 场会议的前提下确定所有相同原色的变色龙。

实现细节
你需要提交一个文件。
这个文件的名字应为 。这个程序应当包含 ,且其中需要实现以下函数:

对于每组测试数据,保证这个函数会被恰好调用一次。
其参数 为题目中的 ,性别为 X 的变色龙的个数。
你的程序可以调用以下函数:

你可以通过调用这个函数组织一场会议。
参数 是参加这场会议的变色龙的列表。
返回值即为本场会议所有变色龙的肤色的种类数。
中的每个元素都应该是一个 内的整数,否则你的程序将会被判为 Wrong Answer [1]。
中的元素不得重复,否则你的程序将会被判为 Wrong Answer [2]。
你的程序不应调用 函数超过 次,否则你的程序将会被判为 Wrong Answer [3]。

你可以通过调用这个函数回答一对原色相同的变色龙。
参数 和 表示变色龙 的原色相同。
必须保证 ,否则你的程序将会被判为 Wrong Answer [4]。
你的程序不得以相同的 值或 值调用此函数两次及以上,否则你的程序将会被判为 Wrong Answer [5]。
如果 的原色不同,你的程序将会被判为 Wrong Answer [6]。
你的程序应当调用 函数恰好 次,否则你的程序将会被判为 Wrong Answer [7]。
实现提示
你的程序可以实现其他函数供内部使用,或使用全局变量。
你的程序不得访问标准输入输出流,也不得通过任何方法访问其他文件。不过,你的程序可以输出到标准错误流。
编译与运行
你可以在附加文件中下载到一个压缩文件,其中包含一个样例交互库以测试你的程序。这个压缩文件也包含了一个你应当提交的程序的样例。
样例交互库为 。为了测试你的程序,请把 放在同一目录下,并执行以下命令来编译你的程序:

若编译成功,会在当前目录生成一个可执行文件 。
请注意样例交互库并非实际交互库。样例交互库仅是由一个单一的,从标准输入流读入数据,并将结果输出到标准输出流的过程构成的。

输入格式
样例交互库从标准输入流读入以下数据:

第一行,一个整数 。
第二行, 个整数 ,表示每只变色龙的性别,若其为 则性别为 X,否则为 Y。
第三行, 个整数 ,表示每只变色龙的原色,保证其在 内。
第四行, 个整数 ,表示每只变色龙的恋爱对象。

输出格式
样例交互库向标准输入流输出以下数据:

若你的程序是正确的,样例交互库将以 的形式输出你调用 的次数。
若你的程序被判为 Wrong Answer,样例交互库将以 的形式输出其类型。
若你的程序触发了不止一种 Wrong Answer,样例交互库只会输出其中一种。

样例
样例输入 1
4
1 0 1 0 0 1 1 0
4 4 1 2 1 2 3 3
4 3 8 7 6 5 2 1
样例调用
调用 子调用 返回值
在附加文件中, 满足第一个子任务的限制, 满足第四个子任务的限制。

数据范围与提示
对于所有数据,满足:




对于每个 ,存在一个唯一的 满足 。
对于每个 ,存在一个唯一的 满足 。




详细子任务附加条件及分值如下表:

子任务编号 附加限制 分值

显示分类标签

思路

首先考虑n^2怎么做
我们两两做一次询问,答案为1的就连一条边(这代表他们要么同色,要么是单恋)
可以发现,点的度数要么为1,要么为3,分类讨论一下即可

然后看左边为X右边为Y的
很显然,最后的图是一个二分图,X一边,Y一边。
我们考虑前n个(都是X),对于每一个,用二分的方式查找它的连边即可

考虑原题
受到刚才的启发,我们会想到把XY分开,可是我想不到怎么分……
demo,我们有更巧的方法:我们只要分开之后满足是二分图就可以二分找边了
开两个队列,如果一个元素与队列1有连边,那么进入队列2,否则进入队列1,这样只有队列之间有连边
然后我们把队列2再做处理,处理到没有元素为止,就做完啦

代码

#include "chameleon.h"
#include <bits/stdc++.h>
using namespace std;
typedef vector<int> V;
const int N = 1005;
V e[N];
int v[2][N], vc[2], i, j, x, col[N], n;
void dfs(int x, int c) {
    col[x] = c;
    for (int y : e[x])
        if (col[y] == -1)
            dfs(y, c ^ 1);
}
inline void getcol() {
    memset(col + 1, -1, n << 2);
    for (j = 1; j < i; ++j)
        if (col[j] == -1)
            dfs(j,0);
    memset(vc, 0, 8);
    for (j = 1; j < i; ++j) v[col[j]][++vc[col[j]]] = j;
}
bool b[N];
int a[N], xb;
bool ck(int o, int l, int r) {
    a[xb = 1] = i;
    for (j = l; j <= r; ++j)
        if (!b[v[o][j]])
            a[++xb] = v[o][j];
    return Query(V(a + 1, a + xb + 1)) < xb;
}
int ask(int o, int l, int r) {
    if (l == r)
        return v[o][l];
    int m = l + r >> 1;
    return ck(o, l, m) ? ask(o, l, m) : ask(o, m + 1, r);
}
void solve(int nn) {
    n = nn * 2;
    for (i = 1; i <= n; ++i) {
        cerr << i << endl;
        if (i == 7)
            ++i, --i;
        getcol();
        memset(b + 1, 0, n);
        for (int o = 0; o < 2; ++o)
            for (; ck(o, 1, vc[o]);) x = ask(o, 1, vc[o]), e[i].push_back(x), e[x].push_back(i), b[x] = 1;
    }
    srand(114514);
    static int too[N];
    for (i = 1; i <= n; ++i)
        if (e[i].size() == 3) {
            random_shuffle(e[i].begin(), e[i].end());
            for (j = 0; j < 3; ++j) {
                V v = { i };
                for (int k = 0; k < 3; ++k)
                    if (j != k)
                        v.push_back(e[i][k]);
                if (Query(v) == 1) {
                    too[i] = e[i][j];
                    break;
                }
            }
        }
    for (i = 1; i <= n; ++i)
        if (too[i]) {
            e[i].erase(find(e[i].begin(), e[i].end(), too[i]));
            e[too[i]].erase(find(e[too[i]].begin(), e[too[i]].end(), i));
        }
    for (i = 1; i <= n; ++i) assert(e[i].size() == 1);
    for (i = 1; i <= n; ++i)
        if (i < e[i][0])
            Answer(i, e[i][0]);
}
void Solve(int n) { solve(n); }