击败构造题

Penchick and BBQ Buns

题面

思路

发现输出的范围高达 \(10^6\),首先 \(1\) 是完全平方数,于是我们对于所有偶数都可以用 \(1,1,2,2,\dots,\frac{n}{2},\frac{n}{2}\) 来构造。但是这是偶数的情况,我们怎么把他扩展到奇数并证明无解性呢?

我们先来讨论第一个问题,考虑奇数由偶数情况改变而来,我们可以令一个数出现三次,如果我们想让这种情况合法,我们设 \(x\)\(pl_{2}-pl_{1}\) 的算术平方根,\(y\)\(pl_{3}-pl_{2}\) 的算术平方根(\(pl\) 代表位置),就必须让以下条件合法:

\[\begin{aligned} x^2+y^2=z^2 \end{aligned} \]

那么我们 \(x,y\) 就可以是勾股数,我们都知道最小的一组勾股数是 \(3,4,5\),这要求我们至少有 \(n\ge 27\),因为 \(n\) 是奇数。因此,我们只需要在这个填发上构造出一种填偶数的方法即可,即对于所有奇数问题,如果我们可以有一个奇数 \(k(n\ge 27)\),使得 \(n=k\) 可以构造出一个合法方案,那么 \(n\le k\) 也一定可以。此时 \(k=27\) 就可以,于是我们不用再继续讨论更多勾股数。因此我们有了做法,手模一遍 \(n=27\) 的构造,然后对于 \(n\ge 27\) 的奇数,只需要在 \(n=27\) 序列的基础上输出成对的连续自然数即可。

现在我们来讨论 \(n<27\) 的奇数的无解性,首先 \(n\) 如果为奇数,就一定出现了出现次数为奇数的数(\(\ne 1\)),若有只有一个奇数,且它的出现次数不为我们刚刚讨论过的 \(3\),那就是一个高维的勾股数,最小解的最小要求位置一定大于刚刚我们算过最小有解 \(n=27\),所以没有用,不用管。如果有多个奇数,由于有多个不知道几维的勾股数,所以它的要求长度一定大于等于 \(27\)

至此我们解决了这题,对于所有偶数,输出成对的自然数,对于小于 \(27\) 的奇数,无解,对于所有大于等于 \(27\) 的奇数,输出人为构造的 \(n=27\) 的序列,然后输出成对的自然数即可。

Code
// Problem: C. Penchick and BBQ Buns
// Contest: Codeforces - Codeforces Round 987 (Div. 2)
// URL: https://codeforces.com/contest/2031/problem/C
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define int long long
#define upp(a, x, y) for (int a = x; a <= y; a++)
#define dww(a, x, y) for (int a = x; a >= y; a--)
#define pb(x) push_back(x)
#define endl '\n'
#define x first
#define y second
#define PII pair<int, int>
using namespace std;
const int N = 2e5 + 10;
int n, tt;
int a[28] = {-114514, 1, 2, 2, 3, 3,  4,  4,  5,  5,  1,  6,  6, 7,
             7,       8, 8, 9, 9, 10, 10, 11, 11, 12, 13, 13, 1, 12};
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> tt;
    while (tt--) {
        cin >> n;
        if (n % 2 == 0) {
            upp(i, 1, n / 2) cout << i << ' ' << i << ' ';
            cout << endl;
        } else {
            if (n < 27) {
                cout << "-1" << endl;
                continue;
            }
            upp(i, 1, 27) cout << a[i] << ' ';
            upp(i, 1, (n - 27) / 2) cout << i + 13 << ' ' << i + 13 << ' ';
            cout << endl;
        }
    }
    return 0;
}

Carousel

题面

秘诀——手玩样例

我们来看这组自造样例:

\[2,2,3,4,4,5,6,7,8,8,8,2 \]

在玩的时候,我们贪心的改变颜色,此时发现对于一个大小大于 \(1\),类型相同的连通块的两边总是可以取到与左边或者右边颜色不同的颜色,因为对于连通块的两边,我们只需要与类型不相同的那两个点颜色不相同就行了,对于另外两边,由于类型相同,不做额外要求。

举个例子:

\[2,1,1,1,3 \]

这里最左边的 \(1\),只需要和 \(2\) 的类型不同即可,对于这个 \(1\),旁边的 \(1\),既可以相同,也可以不同,不做要求。

对于最右边的 \(1\),也是同理。

这里发现,只要我们类型联通块大小大于 \(1\),并且总颜色数大于 \(1\),我们就可以不考虑连通块与周围的联系,因为总是可以取不一样的颜色。

对于颜色数等于 \(1\),有且仅有只有一个类型时才可以做到,我们特判即可。

现在考虑散点的情况,

\[2,2,1,3,4,5,7,7 \]

我们可以贪心的用 \(1,2,1,2,\dots\) 这种方式来染色。

但是这也带来了一个问题当序列没有刚刚那样的连通块,并且序列为奇数长度时,我们没办法再继续这样,不然作为一个环,会出现矛盾。

于是我们只能用三种颜色来涂,即像刚刚那样涂,但最后一个颜色为 \(3\)

如果没有连通块,但是是偶数个,直接 \(1,2,1,2,\cdots\) 就行了。

易证,这种绝对最优。

这时候问题就基本解决了,但是我们还剩一个问题,就是我们是在一个环上,我们应该从哪里开始遍历呢?

答案是我们寻找连通块的起始点然后从这个点决定颜色,然后 DFS 来填充通过这个点的颜色能确定的所有颜色即可,注意先向一边走到头,因为两边有可能在一个点重合,如果是这样,我们可以先决定一边的颜色,遍历到另一边,不让另一边取到与这一边冲突的颜色。

比如:

\[1,2,2,1 \]

如果我们两个 \(2\) 全部涂 \(1\),那么按照构造方法,一定两个 \(1\) 都是 \(0\) 造成冲突。

但是如果先走一边,就有 \(0,1,0,1\),合法。

然后遍历填充方法就是我们上面讨论的方法。

Code
// Problem: D. Carousel
// Contest: Codeforces - Codeforces Round 629 (Div. 3)
// URL: https://codeforces.com/problemset/problem/1328/D
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define int long long
#define upp(a, x, y) for (int a = x; a <= y; a++)
#define dww(a, x, y) for (int a = x; a >= y; a--)
#define pb(x) push_back(x)
#define endl '\n'
#define x first
#define y second
#define PII pair<int, int>
using namespace std;
const int N = 2e5 + 10;
int a[N], n, c[N];
int st[N];
int pr(int x) {
    if (x == 1) return n;
    return x - 1;
}
int ne(int x) {
    if (x == n) return 1;
    return x + 1;
}
bool check(int x) {
    if (st[pr(x)] != st[x] || st[ne(x)] != st[x]) return 1;
    return 0;
}
void dfs(int x, int color, int all) {
    if ((!check(x) && st[x] != all) || c[x]) return;
    // cout << "!!" << x << ' ' << color << ' ' << all << endl;
    c[x] = color;
    if (c[pr(x)] == 0) dfs(pr(x), 3 - color, all);
    if (c[ne(x)] == 0) dfs(ne(x), 3 - color, all);
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int tt;
    cin >> tt;
    while (tt--) {
        upp(i, 0, n) a[i] = c[i] = st[i] = 0;
        int flag1 = 1, flag2 = 1;
        map<int, bool> ha;
        cin >> n;
        upp(i, 1, n) {
            cin >> a[i];
            if (i != 1 && a[i] != a[i - 1]) flag1 = 0;
        }
        if (flag1) {
            cout << "1" << endl;
            upp(i, 1, n) cout << 1 << ' ';
            cout << endl;
            continue;
        }
        upp(i, 1, n) {
            if (st[i]) {
                flag2 = 0;
            } else if (i != n) {
                int sum = 1;
                queue<int> qq;
                qq.push(i);
                while (qq.size()) {
                    auto iter = qq.front();
                    st[iter] = i;
                    qq.pop();
                    if (a[iter] == a[iter + 1]) qq.push(iter + 1);
                }
            }
        }
        if (flag2 && a[n] != a[1] && n % 2) {
            cout << "3" << endl;
            upp(i, 1, n - 1) cout << i % 2 + 1 << ' ';
            cout << 3 << endl;
            continue;
        } else if (flag2) {
            cout << "2" << endl;
            upp(i, 1, n) cout << i % 2 + 1 << ' ';
            continue;
        }
        cout << "2" << endl;
        upp(i, 1, n) {
            if (st[ne(i)] == st[i]) {
                dfs(i, 1, st[i]);
            }
        }
        upp(i, 1, n) cout << c[i] << ' ';
        cout << endl;
    }
    return 0;
}

Send Boxes to Alice (Hard Version)

题面

思路

Motivation 1:我不想做幽默的,没有方向的调整,我想知道 \(p\) 具体是多少,以此让我有目的的调整。

我们考虑枚举 \(p(k\ne 1)\)\(p\) 是原数组总和的因数。显然 \(p\) 一定是这种数,但是实际上 \(p\) 还是有太多的选择了,我们先考虑以下 \(p\) 固定的情况,看看有没有性质可以利用。

Motivation 2:一次改变两个格子?我想找到一种更方便的等价问题。

如何可以把相邻的 \(+a,-a\),这个操作,变为一个格子上的操作呢?没错,对于后面原本 \(-a\) 的格子,如果它是前缀和,那么 \(+a,-a\) 就会抵消。于是我们可以考虑前缀和。

现在,操作变为了单个格子 \(+a,-a\)。再来讨论与原问题的关联性,先来考虑必要性,如果 \(1\)\(n\) 的数,全部满足被 \(p\) 整除,前缀和 \(sum\) 的每个格子都被 \(p\) 整除吗?显然的。那充分性呢?如果 \(sum\) 的每个格子都被 \(p\) 整除,原数组的每个格子都被 \(p\) 整除吗?实际上,我们可以这样想:\(sum_{1}\) 即为原数组的元素 \(1\),即元素 \(1\)\(p\) 整除,接下来的 \(sum_{2}\)\(sum_{1}\) 加上原数组的第二个元素,因为 \(sum_{1}\)\(p\) 整除,所以第二个元素也被 \(p\) 整除,以此类推。

现在我们发现这个前缀和的转化问题和原问题等价,但是实际上,我们还有一个东西,我们需要考虑,就是原数组不能为负,那么 \(\exists \;i\in [1,n],sum_{i}>sum_{i+1}\) 就不能成立。

我们可以贪心点,对于 \(sum_{i}\) 如果想让它被 \(p\) 整除,就可以让它减少 \(sum_{i} \mod p\),或者增加 \(p-sum_{i} \mod p\),取最小值。

这个策略满足上文的条件,因为 \(sum_{i+1}\) 原来是大于等于 \(sum_{i}\) 的,如果 \(sum_{i}\) 的决策是减少,不管 \(sum_{i+1}\) 是什么决策,都不会小于 \(sum_{i}\),如果 \(sum_{i}\) 的决策是增加,那么如果 \(sum_{i+1}\) 的决策是增加,一定大于等于 \(sum_{i}\)(必然是贴近最近的比它大的 \(p\) 的倍数),如果是减少。那么 \(sum_{i+1}\) 必然则大于等于 \(sum_{i}\) 贴近的 \(p\) 的倍数,才会做出这个决策,所以减少后也不会小于 \(sum_{i}\)

我们已经讨论了,当 \(p\) 固定时,我们答案就是 \(\sum_{i=1}^{n} \min(sum_{i}\mod p,p-sum_{i} \mod p)\)。再看看有没有什么性质能减少 \(p\) 的枚举范围,当 \(a\)\(b\) 整除时,\(p=b\) 在上式的所有计算必定不优于 \(p=a\) 的计算。所以我们只需要考虑元素和的质因数即可。

代码很好写,这就是构造题。

Code
// Problem: B2. Send Boxes to Alice (Hard Version)
// Contest: Codeforces - Codeforces Round 601 (Div. 1)
// URL: https://codeforces.com/problemset/problem/1254/B2
// Memory Limit: 256 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define int long long
#define upp(a, x, y) for (int a = x; a <= y; a++)
#define dww(a, x, y) for (int a = x; a >= y; a--)
#define pb(x) push_back(x)
#define endl '\n'
#define x first
#define y second
#define PII pair<int, int>
using namespace std;
const int N = 1e6 + 10, INF = 0x3f3f3f3f3f3f3f3f;
int a[N], s[N], n, tt;
int ev(int x) {
    int sum = 0;
    upp(i, 1, n) { sum += min(s[i] % x, x - s[i] % x); }
    return sum;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int ans = INF;
    cin >> n;
    upp(i, 1, n) cin >> a[i], s[i] = s[i - 1] + a[i];
    int sum = s[n];
    map<int, bool> st;
    vector<int> fac;
    upp(i, 2, sum / i) {
        while (sum % i == 0) {
            sum /= i;
            if (!st[i]) {
                fac.pb(i);
                st[i] = 1;
            }
        }
    }
    if (sum != 1) fac.pb(sum);
    upp(i, 0, (int)fac.size() - 1) { ans = min(ans, ev(fac[i])); }
    if (ans == INF)
        cout << "-1" << endl;
    else
        cout << ans << endl;
    return 0;
}

P7915 [CSP-S 2021] 回文

题面

Motivation:手玩样例。

发现实际上,有一个分界点,使得分界点左边全部是 L 操作取得的,右边全部是 R 操作所取得的。

这个分界点的位置在我们最后一个拿的数,而最后一个拿的数就是第一个拿的数。第一个拿的数要么是左边的,要么是右边的,只有两种情况。

接下来,我们设那个最后拿的数的位置是 \(x\),那么此时就相当与我们需要用 \(1\dots x-1\) 的数组成的栈和 \(x+1\dots 2n\) 的数组成的栈每次取一个数,使得取得数的序列是一个回文串。

我们讨论第一个数取左边的情况,右边的情况可以仿照左边构造。

我们有两个栈 \(a,b\),怎么取才能让字典序最小,并且取得回文串呢?每次取 \(a\) 相当于一次 L 操作,取 \(b\) 相当于一次 R 操作。

\(\textbf{Observation 1:}\) 我们如果取得栈顶元素,必然对应着目前的两个栈其中一个的栈底的元素。

因为我们可以知道,目前的取出必然是有对应的,而在目前还未决定的所有元素之中,取的相当于这个子结构的第一个元素,所以与它对应的元素必然不可以不在栈底。

所以考虑以下贪心:把栈看成双端队列,每次考虑两个栈栈顶的元素与两个栈底的元素的相等关系,尽量让 \(a\) 栈参与在前面,因为我们需要字典序最小。如果没有相等的就无解。

比如:

\[a:4,2,3,4\\ b:5,2,3,5 \]

此时应该取 \(a\) 栈顶和栈底的 \((4,4)\)

再比如:

\[a:4,2,3,5\\ b:5,2,3,4 \]

此时应该取 \(a\) 栈顶的 \(4\)\(b\) 栈底的 \(4\)

先取右边情况同理,贪心取法略有不同。

Code
// Problem: P7915 [CSP-S 2021] 回文
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7915
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define int long long
#define upp(a, x, y) for (int a = x; a <= y; a++)
#define dww(a, x, y) for (int a = x; a >= y; a--)
#define pb(x) push_back(x)
#define endl '\n'
#define x first
#define y second
#define PII pair<int, int>
using namespace std;
const int N = 1e6 + 10;
int c[N], n;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int tt;
    cin >> tt;
    while (tt--) {
        cin >> n;
        upp(i, 1, n * 2) cin >> c[i];
        string l, r;
        deque<int> a, b;
        int tmp = 2;
        while (c[tmp] != c[1]) tmp++;
        dww(i, tmp - 1, 2) a.pb(c[i]);
        upp(i, tmp + 1, n * 2) b.pb(c[i]);
        bool flag1 = 1;
        while (a.size() || b.size()) {
            if (a.size() > 1) {
                if (a.front() == a.back()) {
                    l += 'L';
                    r += 'L';
                    a.pop_front();
                    a.pop_back();
                    continue;
                }
            }
            if (b.size() && a.size()) {
                if (a.back() == b.front()) { //前l后r
                    l += 'L';
                    r += 'R';
                    a.pop_back();
                    b.pop_front();
                    continue;
                } else if (a.front() == b.back()) { //先r后l
                    l += 'R';
                    r += 'L';
                    a.pop_front();
                    b.pop_back();
                    continue;
                }
            }
            if (b.size() > 1) {
                if (b.back() == b.front()) {
                    l += 'R';
                    r += 'R';
                    b.pop_back();
                    b.pop_front();
                    continue;
                }
            }
            flag1 = 0;
            break;
        }
        reverse(r.begin(), r.end());
        string ans1 = "L";
        ans1 += l + r;
        ans1 += 'L';
        reverse(c + 1, c + 1 + 2 * n);
        l.clear(), r.clear();
        a.clear(), b.clear();
        tmp = 2;
        while (c[tmp] != c[1]) tmp++;
        dww(i, tmp - 1, 2) a.pb(c[i]);
        upp(i, tmp + 1, n * 2) b.pb(c[i]);
        bool flag2 = 1;
        while (a.size() || b.size()) {
            if (b.size() > 1) {
                if (b.front() == b.back()) {
                    l += 'L';
                    r += 'L';
                    b.pop_front();
                    b.pop_back();
                    continue;
                }
            }
            if (b.size() && a.size()) {
                if (b.back() == a.front()) { //前l后r
                    l += 'L';
                    r += 'R';
                    b.pop_back();
                    a.pop_front();
                    continue;
                } else if (b.front() == a.back()) { //先r后l
                    l += 'R';
                    r += 'L';
                    b.pop_front();
                    a.pop_back();
                    continue;
                }
            }
            if (a.size() > 1) {
                if (a.back() == a.front()) {
                    l += 'R';
                    r += 'R';
                    a.pop_back();
                    a.pop_front();
                    continue;
                }
            }
            flag2 = 0;
            break;
        }
        if (flag2 == 0 && flag1 == 0) {
            cout << -1 << endl;
            continue;
        }
        reverse(r.begin(), r.end());
        string ans2 = "R";
        ans2 += l + r;
        ans2 += 'L';
        if (flag1)
            cout << ans1 << endl;
        else if (flag2)
            cout << ans2 << endl;
        else if (ans1 < ans2)
            cout << ans1 << endl;
        else
            cout << ans2 << endl;
    }
    return 0;
}
posted @ 2025-01-15 19:55  PM_pro  阅读(29)  评论(1)    收藏  举报