把两种倍数位置按 gcd 归一化后,问题转成相邻两个较稀疏倍数之间会强制出现多少个连续稠密颜色,判定 k 是否严格大于这个上界。
OJ: luogu
题目 ID: P6476
难度:普及+/提高
标签:数学最大公约数思维
日期: 2026-06-20 11:56
题意
有无限多个格子。
的倍数必须染红 的倍数必须染蓝 - 同时是两者倍数的位置可以任选红或蓝
忽略未染色格子后,如果出现
题目要求判断:是否存在一种染色方案,使它不是无聊的。
思路
先看一个可以直接验证想法的小数据暴力:
cpp
#include <bits/stdc++.h>
using namespace std;
// brute.cpp:小数据暴力解。
// 对一个周期内的“必染色类型”建状态图,
// 判断是否存在无限染色方案,使得同色连续段长度始终小于 k。
int T;
long long p1, p2, k;
struct State {
int pos;
int last_color; // 0 红, 1 蓝, 2 表示还没有上一段颜色
int run_len;
};
vector<int> type_list;
vector<State> states;
vector<vector<int> > graph, rev_graph, comps;
vector<int> vis, order, comp, has_cycle, reached;
long long gcd_value(long long a, long long b) {
while (b != 0) {
long long r = a % b;
a = b;
b = r;
}
return a;
}
long long lcm_value(long long a, long long b) {
return a / gcd_value(a, b) * b;
}
int get_idx(const vector<vector<int> > &id, int pos, int last_color, int run_len) {
return id[pos][last_color * (int)k + run_len];
}
void dfs1(int u) {
vis[u] = 1;
for (int v : graph[u]) {
if (!vis[v]) {
dfs1(v);
}
}
order.push_back(u);
}
void dfs2(int u, int cid) {
comp[u] = cid;
comps[cid].push_back(u);
for (int v : rev_graph[u]) {
if (comp[v] == -1) {
dfs2(v, cid);
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
while (T--) {
cin >> p1 >> p2 >> k;
if (k == 1) {
cout << "No\n";
continue;
}
type_list.clear();
states.clear();
graph.clear();
rev_graph.clear();
comps.clear();
vis.clear();
order.clear();
comp.clear();
has_cycle.clear();
reached.clear();
long long g = gcd_value(p1, p2);
long long a = p1 / g;
long long b = p2 / g;
long long L = lcm_value(a, b);
for (long long x = 0; x < L; x++) {
bool red = (x % a == 0);
bool blue = (x % b == 0);
if (!red && !blue) {
continue;
}
if (red && blue) {
type_list.push_back(2); // 公共点,可以染红或染蓝
} else if (red) {
type_list.push_back(0);
} else {
type_list.push_back(1);
}
}
int m = type_list.size();
vector<vector<int> > id(m, vector<int>(3 * (int)k, -1));
for (int pos = 0; pos < m; pos++) {
for (int last_color = 0; last_color < 3; last_color++) {
for (int run_len = 0; run_len < k; run_len++) {
int cur_id = states.size();
id[pos][last_color * (int)k + run_len] = cur_id;
states.push_back({pos, last_color, run_len});
}
}
}
graph.assign(states.size(), vector<int>());
rev_graph.assign(states.size(), vector<int>());
for (int i = 0; i < (int)states.size(); i++) {
int pos = states[i].pos;
int last_color = states[i].last_color;
int run_len = states[i].run_len;
vector<int> colors;
if (type_list[pos] == 2) {
colors.push_back(0);
colors.push_back(1);
} else {
colors.push_back(type_list[pos]);
}
for (int color : colors) {
int next_run = (last_color == color ? run_len + 1 : 1);
if (next_run >= k) {
continue;
}
int next_pos = (pos + 1) % m;
int to = get_idx(id, next_pos, color, next_run);
graph[i].push_back(to);
rev_graph[to].push_back(i);
}
}
vis.assign(states.size(), 0);
for (int i = 0; i < (int)states.size(); i++) {
if (!vis[i]) {
dfs1(i);
}
}
comp.assign(states.size(), -1);
int comp_cnt = 0;
for (int i = (int)order.size() - 1; i >= 0; i--) {
int u = order[i];
if (comp[u] == -1) {
comps.push_back(vector<int>());
dfs2(u, comp_cnt);
comp_cnt++;
}
}
has_cycle.assign(comp_cnt, 0);
for (int i = 0; i < comp_cnt; i++) {
if ((int)comps[i].size() > 1) {
has_cycle[i] = 1;
continue;
}
int u = comps[i][0];
for (int v : graph[u]) {
if (v == u) {
has_cycle[i] = 1;
}
}
}
queue<int> q;
reached.assign(states.size(), 0);
vector<int> first_colors;
if (type_list[0] == 2) {
first_colors.push_back(0);
first_colors.push_back(1);
} else {
first_colors.push_back(type_list[0]);
}
bool ok = false;
for (int color : first_colors) {
int start = get_idx(id, 1 % m, color, 1);
if (!reached[start]) {
reached[start] = 1;
q.push(start);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
if (has_cycle[comp[u]]) {
ok = true;
break;
}
for (int v : graph[u]) {
if (!reached[v]) {
reached[v] = 1;
q.push(v);
}
}
}
if (ok) {
cout << "Yes\n";
} else {
cout << "No\n";
}
}
return 0;
}brute.cpp 会把一个周期内的必染色类型抽出来,然后在状态图上判断是否存在无限延续的合法染色方案。
这个做法适合小数据理解,但显然不可能应对
第一步:先除掉 gcd
设:
把所有下标都除以
并设
这样就有:
第二步:谁更容易形成长连续段?
因为
的倍数更密 的倍数更稀
直觉上,真正危险的是“很多个
如果我们把所有公共点都染成较稀疏的那一色,那么蓝色连续段最多只会很短,问题就转化成:
在相邻两个
的倍数之间,最多会出现多少个只能染成红色的点?
第三步:强制红段长度是多少?
看相邻两个
在这两个点之间,所有被染色的内部点都只能是
因为
所以这中间会形成一整段连续红色。
这段长度的最大值是:
用整数写法就是:
第四步:判定条件
如果:
大于这个最大强制同色段长度
那么就可以通过把公共点尽量染成较稀疏的颜色,成功避免出现长度为
否则,无论怎么染,都会被某一段强制红色卡住。
所以答案就是:
- 若
,输出 Yes - 否则输出
No
特殊情况
如果
这时只要
代码
cpp
#include <bits/stdc++.h>
using namespace std;
long long T;
long long p1, p2, k;
long long gcd_value(long long a, long long b) {
while (b != 0) {
long long r = a % b;
a = b;
b = r;
}
return a;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
while (T--) {
cin >> p1 >> p2 >> k;
// 任意一种染色方案都至少会出现长度为 1 的同色段,
// 所以 k=1 时一定无解。
if (k == 1) {
cout << "No\n";
continue;
}
long long g = gcd_value(p1, p2);
long long a = p1 / g;
long long b = p2 / g;
if (a > b) {
swap(a, b);
}
// p1 == p2 时,所有必须染色的格子都是公共点,
// 可以任意交替染色,因此只要 k>1 就一定有解。
if (a == 1 && b == 1) {
cout << "Yes\n";
continue;
}
// 归一化后看成:
// - a 的倍数偏“稠密”,把它当成红色
// - b 的倍数偏“稀疏”,把它当成蓝色
//
// 在两次相邻的 b 倍数之间,里面所有必须染色的点都只能是 a 的倍数,
// 它们会形成一整段连续红色。这个连续段长度的最大值是 ceil((b-1)/a)。
long long max_run = (a + b - 2) / a;
if (k > max_run) {
cout << "Yes\n";
} else {
cout << "No\n";
}
}
return 0;
}复杂度
- 时间复杂度:每组
- 空间复杂度:
总结
这题的关键不是模拟染色,而是先做两个转化:
- 用
gcd把问题归一化 - 把“是否无聊”转成“强制同色段的最大长度是多少”
一旦发现真正的瓶颈是相邻两个稀疏倍数之间的那段连续稠密颜色,题目就只剩一个简单公式判定。