按 PRINT 数量分成 degree 1/2/3,分别检查全相同、块循环和循环体切分。
OJ: usaco
题目 ID: 1493
难度:普及/提高-
标签:递归枚举思维usaco
日期: 2026-07-11 15:14
题意
有一种简单语言:
PRINT c输出一个数字c。REP o ... END把内部程序重复执行o次。
REP 数量不限,但 PRINT 语句最多只能使用
其中
思路
先看一个更贴近程序定义的小数据判断:
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-11 15:14
* update_at: 2026-07-11 15:17
*/
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 15;
const int MAXK = 3;
int T;
int n, k;
int a[MAXN];
int memo[MAXN][MAXN][MAXK + 1]; // 0 未计算,1 false,2 true。
bool is_period(int l, int r, int len) {
int total = r - l + 1;
if (total % len != 0) return false;
for (int i = l; i <= r; i++) {
int base = l + (i - l) % len;
if (a[i] != a[base]) return false;
}
return true;
}
bool can_print(int l, int r, int print_limit) {
if (l > r) return true;
if (print_limit == 0) return false;
if (memo[l][r][print_limit] != 0) {
return memo[l][r][print_limit] == 2;
}
if (l == r) {
memo[l][r][print_limit] = 2;
return true;
}
int len = r - l + 1;
// 枚举一个 REP 语句:整个区间是否由某个更短程序重复得到。
for (int body_len = 1; body_len < len; body_len++) {
if (!is_period(l, r, body_len)) continue;
if (can_print(l, l + body_len - 1, print_limit)) {
memo[l][r][print_limit] = 2;
return true;
}
}
// 枚举程序中语句序列的分界点:左程序 + 右程序。
for (int mid = l; mid < r; mid++) {
for (int left_prints = 1; left_prints < print_limit; left_prints++) {
int right_prints = print_limit - left_prints;
if (can_print(l, mid, left_prints) &&
can_print(mid + 1, r, right_prints)) {
memo[l][r][print_limit] = 2;
return true;
}
}
}
memo[l][r][print_limit] = 1;
return false;
}
void solve_one() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
memset(memo, 0, sizeof(memo));
cout << (can_print(1, n, k) ? "YES" : "NO") << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
while (T--) {
solve_one();
}
return 0;
}这个暴力用 can_print(l,r,p) 表示区间 [l,r] 能否用不超过 p 个 PRINT 输出。它枚举两种程序结构:两个子程序拼接,或者某个更短程序被 REP 重复。这个模型贴近题意,但正式解法可以利用
把“能用不超过 PRINT 输出”称为 degree
degree 1
只用一个 PRINT,输出的所有数字必须相同。
所以 check1(l,r) 只需要检查区间是否全相同。
degree 2
两个 PRINT 可以形成两个连续片段,然后这个循环体被 REP 重复。
把序列压缩成连续块,例如:
1 1 1 2 2 1 1 1 2 2
=> (1,3), (2,2), (1,3), (2,2)如果它是 degree 2,那么块序列应该每隔两个重复一次。也就是第
块数为 1 或 2 时也可以直接输出。
degree 3
对 REP 的循环体长度 len。如果整个序列不是由长度为 len 的前缀重复得到,就跳过。
如果找到了一个循环体,还需要判断这个循环体能否拆成:
- degree 1 + degree 2
- 或 degree 2 + degree 1
所以再枚举循环体内部的切分点,调用 check1 和 check2 即可。
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-11 15:14
* update_at: 2026-07-11 15:17
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
int T;
int n, k;
int a[MAXN];
int block_value[MAXN], block_len[MAXN];
bool check1(int l, int r) {
if (l > r) return true;
for (int i = l + 1; i <= r; i++) {
if (a[i] != a[l]) return false;
}
return true;
}
bool check2(int l, int r) {
if (l > r) return true;
if (check1(l, r)) return true;
int block_cnt = 0;
for (int i = l; i <= r; i++) {
if (i == l || a[i] != a[i - 1]) {
block_cnt++;
block_value[block_cnt] = a[i];
block_len[block_cnt] = 1;
} else {
block_len[block_cnt]++;
}
}
if (block_cnt <= 2) return true;
if (block_cnt % 2 == 1) return false;
for (int i = 1; i + 2 <= block_cnt; i++) {
if (block_value[i] != block_value[i + 2]) return false;
if (block_len[i] != block_len[i + 2]) return false;
}
return true;
}
bool is_repeated_block(int l, int r, int len) {
int total = r - l + 1;
if (total % len != 0) return false;
for (int i = l; i + len <= r; i++) {
if (a[i] != a[i + len]) return false;
}
return true;
}
bool check3(int l, int r) {
int total = r - l + 1;
// 枚举外层 REP 的循环体长度。
for (int len = 1; len <= total; len++) {
if (!is_repeated_block(l, r, len)) continue;
int body_l = l;
int body_r = l + len - 1;
// 循环体由 degree 1 + degree 2,或 degree 2 + degree 1 组成。
for (int cut = 0; cut <= len; cut++) {
int left_l = body_l;
int left_r = body_l + cut - 1;
int right_l = body_l + cut;
int right_r = body_r;
if (check1(left_l, left_r) && check2(right_l, right_r)) return true;
if (check2(left_l, left_r) && check1(right_l, right_r)) return true;
}
}
return false;
}
void solve_one() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
bool ok = false;
if (k == 1) ok = check1(1, n);
else if (k == 2) ok = check2(1, n);
else ok = check3(1, n);
cout << (ok ? "YES" : "NO") << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> T;
while (T--) {
solve_one();
}
return 0;
}复杂度
check1 和 check2 都是线性的;check3 按官方分析可视为
空间复杂度为
总结
本题的关键是不要枚举程序文本,而是按 PRINT 数量分析输出序列的结构。