Moo Language

GitHub跳转原题关系图返回列表

枚举一类句和二类句数量,最大化可用连接词和逗号扩展名词后构造段落。

OJ: usaco

题目 ID: 1324

难度:普及/提高-

标签:构造枚举模拟字符串usaco

日期: 2026-07-11 16:31

题意

给定一个词库,单词分为四类:

  • 名词 noun
  • 及物动词 transitive-verb
  • 不及物动词 intransitive-verb
  • 连接词 conjunction

可以使用的标点只有逗号和句号,数量分别为 CCPP

合法句子有两种:

  • 一类句:名词 + 不及物动词
  • 二类句:名词 + 及物动词 + 名词列表

两个句子可以用一个连接词合成一个复合句,但复合句不能继续合并。每个句子或复合句最后都要有句号。

要求构造一个合法段落,使使用的单词数量最大,并输出最大单词数和任意一个合法段落。

思路

先看一个小数据暴力,它直接枚举一类句、二类句、连接词和额外名词数量:

cpp
/**
 * 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 16:31
 * update_at: 2026-07-11 16:35
 */
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

int T;
int n, comma_cnt, period_cnt;
int noun_cnt, trans_cnt, intrans_cnt, conj_cnt;
vector<string> nouns;
vector<string> trans_verbs;
vector<string> intrans_verbs;
vector<string> conjs;

string take_word(vector<string> &words) {
    string res = words.back();
    words.pop_back();
    return res;
}

void construct_output(int best_words, int best_type1, int best_type2, int best_combine, int best_extra) {
    cout << best_words << '\n';
    if (best_words == 0) {
        cout << '\n';
        return;
    }

    vector<string> basic_sentences;
    for (int i = 1; i <= best_type1; i++) {
        basic_sentences.push_back(take_word(nouns) + " " + take_word(intrans_verbs));
    }
    for (int i = 1; i <= best_type2; i++) {
        basic_sentences.push_back(take_word(nouns) + " " + take_word(trans_verbs) + " " + take_word(nouns));
    }

    if (best_extra > 0) {
        int last = (int)basic_sentences.size() - 1;
        for (int i = 1; i <= best_extra; i++) {
            basic_sentences[last] += ", " + take_word(nouns);
        }
    }

    vector<string> final_sentences;
    int idx = 0;
    for (int i = 1; i <= best_combine; i++) {
        final_sentences.push_back(basic_sentences[idx] + " " + take_word(conjs) + " " + basic_sentences[idx + 1] + ".");
        idx += 2;
    }
    while (idx < (int)basic_sentences.size()) {
        final_sentences.push_back(basic_sentences[idx] + ".");
        idx++;
    }

    for (int i = 0; i < (int)final_sentences.size(); i++) {
        if (i > 0) cout << ' ';
        cout << final_sentences[i];
    }
    cout << '\n';
}

void solve_one() {
    int best_words = 0;
    int best_type1 = 0;
    int best_type2 = 0;
    int best_combine = 0;
    int best_extra = 0;

    // 小数据暴力:枚举一类句、二类句、连接词和额外名词数量。
    for (int type1 = 0; type1 <= intrans_cnt; type1++) {
        for (int type2 = 0; type2 <= trans_cnt; type2++) {
            for (int combine = 0; combine <= conj_cnt; combine++) {
                for (int extra = 0; extra <= comma_cnt; extra++) {
                    if (type1 + 2 * type2 + extra > noun_cnt) continue;
                    if (extra > 0 && type2 == 0) continue;

                    int sentence_cnt = type1 + type2;
                    if (combine > sentence_cnt / 2) continue;
                    if (sentence_cnt - combine > period_cnt) continue;

                    int words = 2 * type1 + 3 * type2 + combine + extra;
                    if (best_words < words) {
                        best_words = words;
                        best_type1 = type1;
                        best_type2 = type2;
                        best_combine = combine;
                        best_extra = extra;
                    }
                }
            }
        }
    }

    construct_output(best_words, best_type1, best_type2, best_combine, best_extra);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> T;
    while (T--) {
        cin >> n >> comma_cnt >> period_cnt;
        noun_cnt = trans_cnt = intrans_cnt = conj_cnt = 0;
        nouns.clear();
        trans_verbs.clear();
        intrans_verbs.clear();
        conjs.clear();
        for (int i = 1; i <= n; i++) {
            string word, type;
            cin >> word >> type;
            if (type == "noun") {
                noun_cnt++;
                nouns.push_back(word);
            } else if (type == "transitive-verb") {
                trans_cnt++;
                trans_verbs.push_back(word);
            } else if (type == "intransitive-verb") {
                intrans_cnt++;
                intrans_verbs.push_back(word);
            } else {
                conj_cnt++;
                conjs.push_back(word);
            }
        }

        solve_one();
    }

    return 0;
}

正解也可以沿用这个思路,只是枚举范围本身已经足够小。设:

  • type1:一类句数量。
  • type2:基础二类句数量,每个基础二类句用 名词 + 及物动词 + 名词
  • combine:用连接词合并的句子对数。
  • extra:接到某个二类句后面的额外名词数量,每个额外名词需要一个逗号。

对于固定的 type1type2

text
需要名词数 = type1 + 2 * type2
基础单词数 = 2 * type1 + 3 * type2
基础句子数 = type1 + type2

连接词最多能合并 floor(基础句子数/2)floor(基础句子数 / 2) 对句子,同时不能超过词库中的连接词数量。

合并后需要的句号数为:

text
基础句子数 - combine

如果句号不够,这个方案不可行。

如果至少有一个二类句,还可以把剩余名词用逗号接到最后一个二类句后面。额外名词数量为:

text
min(剩余名词数, C)

于是当前方案使用单词数为:

text
2 * type1 + 3 * type2 + combine + extra

枚举所有 type1type2,记录单词数最大的方案。

构造时:

  1. 先生成所有一类句。
  2. 再生成所有基础二类句。
  3. 如果有额外名词,把它们用逗号接到最后一个二类句后面。
  4. 把前 2combine2 * combine 个基础句两两用连接词合并。
  5. 剩下的基础句直接加句号输出。

代码

cpp
/**
 * 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 16:31
 * update_at: 2026-07-11 16:35
 */
#include <bits/stdc++.h>
using namespace std;

vector<string> nouns;
vector<string> trans_verbs;
vector<string> intrans_verbs;
vector<string> conjs;

int T;
int n, comma_cnt, period_cnt;

string take_word(vector<string> &words) {
    string res = words.back();
    words.pop_back();
    return res;
}

void solve_one() {
    cin >> n >> comma_cnt >> period_cnt;

    nouns.clear();
    trans_verbs.clear();
    intrans_verbs.clear();
    conjs.clear();

    for (int i = 1; i <= n; i++) {
        string word, type;
        cin >> word >> type;
        if (type == "noun") {
            nouns.push_back(word);
        } else if (type == "transitive-verb") {
            trans_verbs.push_back(word);
        } else if (type == "intransitive-verb") {
            intrans_verbs.push_back(word);
        } else {
            conjs.push_back(word);
        }
    }

    int noun_total = (int)nouns.size();
    int trans_total = (int)trans_verbs.size();
    int intrans_total = (int)intrans_verbs.size();
    int conj_total = (int)conjs.size();

    int best_words = 0;
    int best_type1 = 0;
    int best_type2 = 0;
    int best_combine = 0;
    int best_extra = 0;

    for (int type1 = 0; type1 <= intrans_total; type1++) {
        for (int type2 = 0; type2 <= trans_total; type2++) {
            int used_nouns = type1 + 2 * type2;
            if (used_nouns > noun_total) continue;

            int sentence_cnt = type1 + type2;
            int combine = min(conj_total, sentence_cnt / 2);
            int need_period = sentence_cnt - combine;
            if (need_period > period_cnt) continue;

            int extra = 0;
            if (type2 > 0) {
                extra = min(comma_cnt, noun_total - used_nouns);
            }

            int words = 2 * type1 + 3 * type2 + combine + extra;
            if (words > best_words) {
                best_words = words;
                best_type1 = type1;
                best_type2 = type2;
                best_combine = combine;
                best_extra = extra;
            }
        }
    }

    cout << best_words << '\n';
    if (best_words == 0) {
        cout << '\n';
        return;
    }

    vector<string> basic_sentences;

    for (int i = 1; i <= best_type1; i++) {
        string sentence = take_word(nouns) + " " + take_word(intrans_verbs);
        basic_sentences.push_back(sentence);
    }

    for (int i = 1; i <= best_type2; i++) {
        string sentence = take_word(nouns) + " " + take_word(trans_verbs) + " " + take_word(nouns);
        basic_sentences.push_back(sentence);
    }

    if (best_extra > 0) {
        int last = (int)basic_sentences.size() - 1;
        for (int i = 1; i <= best_extra; i++) {
            basic_sentences[last] += ", " + take_word(nouns);
        }
    }

    vector<string> final_sentences;
    int idx = 0;
    for (int i = 1; i <= best_combine; i++) {
        string sentence = basic_sentences[idx] + " " + take_word(conjs) + " " + basic_sentences[idx + 1] + ".";
        final_sentences.push_back(sentence);
        idx += 2;
    }
    while (idx < (int)basic_sentences.size()) {
        final_sentences.push_back(basic_sentences[idx] + ".");
        idx++;
    }

    for (int i = 0; i < (int)final_sentences.size(); i++) {
        if (i > 0) cout << ' ';
        cout << final_sentences[i];
    }
    cout << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> T;
    while (T--) {
        solve_one();
    }

    return 0;
}

复杂度

设词库大小为 NN

枚举 type1type2 的复杂度为 O(N2)O(N^2),构造输出为 O(N)O(N)

空间复杂度为 O(N)O(N)

总结

本题的难点不在语法本身,而在把语法资源转成几个数量约束。

先确定最优的句子数量组合,再按组合构造段落,可以让输出细节变得可控。