闲话 22.10.4

闲话

今日休息
所以我看着一堆人在打PCK
“码力是很重要的”
所以我没去打
于是今天的杂题有两道!
写完这句话后开了第三题 发现这是nmd结论题
于是今天的杂题有三道

我是什么废物两个小时实现不出来这个结论
于是今天的杂题还是两道
行吧我题读错了 怪不得我模不出样例
所以今天的杂题还是三道

似乎上次自由时间我写了 4 道题来着
所以我在退步(确信)

晚饭回来更新:
路上在躲跑餐的学生们
然后往旁边一躲
脚下躺着的楼号牌:不好意思,这里满员了
然后踩了上去
然后一滑
膝盖:Full Combo~


70億の秘密と同じだけの

純粋が欲しくなるのは何故?

何が正義か

そんな馬鹿正直なふたりが

わからないよと泣いてる

杂题

蓝智力相当高,尤其擅长数学。据说连人类所无法想象程度的计算都能够在瞬间完成。——《东方求闻史纪》

CF1687D

定义 \(f(x)\) 表示严格大于 \(x\) 的最小的完全平方数,定义 \(g(x)\) 为小于等于 \(x\) 的最大的完全平方数。例如,\(f(1)=f(2)=g(4)=g(8)=4\)。蓝认为,一个正整数是“可爱”的,当且仅当 \(x-g(x)<f(x)-x\),例如,\(1,5,11\) 是可爱的正整数,而 \(3,8,15\) 不是。

蓝给了你一个长度为 \(n\) 的正整数数列 \(a_i\),你需要帮她找到最小的非负整数 \(k\),使得 \(\forall\ i\)\(a_i+k\) 是可爱的。

\(n \le 10^6, a_i \le a_{i+1} \le 2\times 10^6\)

打表题。性质题。

观察 打表 经过严密的数学证明可得,可爱的数组成了一段段区间,第 \(x\) 段区间可以表为 \([x^2,x(x+1)]\)
\(a_1\) 位于第 \((a_n - a_1)\) 段区间内时定能得到答案,因此 \(k < (a_n - a_1)^2\)

假设 \(a_1\) 被放在了第 \(v\) 段内。可以发现 \(k\) 共有 \(v + 1\) 种取值。
\(k\) 变化时,可能存在原来是可爱数的 \(a_i + k\) 变为不可爱的数,反之亦然。
当一个元素的性质发生变化时,就无法通过 \(k\) 的继续变化使得其性质发生第二次变化。这是因为 \(v\) 段后的所有段长度定 \(> v\),其长度大于 \(k\) 可能的变化范围。
可以发现,这其实限制了 \(k\) 的上下界。

我们初始时令 \(a_1 + k\) 位于第 \(v\) 段的段首。然后我们向后扫每一段
对于可爱段,我们找到这段内最大的数,这个数标定了 \(k\) 取值的上界,因为不能让这个数出可爱段。对于不可爱段,我们找到这段内最小的数,这个数标定了 \(k\) 取值的下界,因为需要让这个数增加到离开不可爱段。
于是我们每次扫一下就能得到答案。

这么写的复杂度似乎是 \(a_n \log a_n\),具体是不是线性没想。

code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,a,b) for (register int (i) = (a); (i) <= (b); ++(i))
#define pre(i,a,b) for (register int (i) = (a); (i) >= (b); --(i))
const int N = 1e6 + 10;
int n, a[N], vis[N<<2], nxt[N<<2], pref[N<<2];

signed main() {
    ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
    cin >> n; rep(i,1,n) cin >> a[i];
    rep(i,2,n) vis[a[i] - a[1]] = 1;
    nxt[4000001] = 1e7, pref[0] = -1;
    rep(i,1,4000000) if (vis[i]) pref[i] = i; else pref[i] = pref[i-1];
    pre(i,4000000,1) if (vis[i]) nxt[i] = i;  else nxt[i] = nxt[i+1];

    rep(i,1,a[n]) {
        if (i * (i+1) < a[1]) continue;
        int l = 0, r = i; if (a[1] > i * i) l = a[1] - i * i;
        
        int nowl = 0, nowr = i;
        for (int j = i ; ; j++) {
            if (pref[nowr] >= nowl) r = min(r, nowr - pref[nowr]);
            nowl += 2 * j + 1;
            if (nxt[nowr+1] < nowl) l = max(l, nowl - nxt[nowr+1]);
            nowr += 2 * j + 2;
            if (l > r or nowl > a[n] - a[1]) break;
        }

        if (l <= r) {
            cout << i * i + l - a[1] << '\n';
            return 0; 
        }
    }
}



CF1700F

给定两个 \(2 \times n\)\(01\) 矩阵 \(A\)\(B\),定义一次操作为交换 \(A\) 中任意两个相邻的位置中的值,输出使得 \(A=B\) 的最小操作次数,如果无法使 \(A=B\) 则输出 \(-1\)

\(n \le 2\times 10^5\).

贪心题。

当矩阵是 \(1\times n\) 的时候,答案是易求的。
\(suma_i = \sum_{j=1}^i a[i], sumb_i = \sum_{j=1}^i b[i]\),则最终答案就是

\[\sum_{i=1}^n |suma_i - sumb_i| \]

证明考虑 \(a\) 那边多 \(k\) 个 1 的情况,直到 \(b\) 那边出现 1 为止都得带着这 \(k\) 个 1 一直移动,移动一格的花费是 \(k = suma_i - sumb_i\)\(b\) 多 1 的情况同理,因此加绝对值。

现在矩阵是 \(2\times n\) 的了,我们需要得到上下交换的最小花费。
分别记录两个前缀和 \(sum_1\)\(sum_2\) 分别表示两矩阵第一行的前缀和之差与第二行的前缀和之差。
在当前情况下上下交换当且仅当 \(sum_1\)\(sum_2\) 异号。当同号时上下交换是不必要的,这时交换肯定不会使答案更优。
异号时应当上下移动至 \(sum_1\)\(sum_2\) 之一为 0。这样做可以使得全局左右交换的次数最少,肯定不会使答案更劣。
上下移动后应当在答案中加入 \(sum_1 + sum_2\),原因同 \(1\times n\) 理。

记得开ll。

code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for (register int (i) = (a); (i) <= (b); ++(i))
#define pre(i,a,b) for (register int (i) = (a); (i) >= (b); --(i))
#define sgn(u) ((u == 0) ? 0 : (u > 0) ? 1 : -1 )
const int N = 2e5 + 10;
int n, a[2][N], b[2][N];
long long cnt, ans;

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n;
    rep(i,0,1) rep(j,1,n) cin >> a[i][j], cnt += a[i][j];
    rep(i,0,1) rep(j,1,n) cin >> b[i][j], cnt -= b[i][j];
    if (cnt) { cout << -1 << '\n'; return 0; }
    long long sum0 = 0, sum1 = 0, delt;
    rep(i,1,n) {
        sum0 += a[0][i] - b[0][i], sum1 += a[1][i] - b[1][i];
        if (1ll * sum0 * sum1 < 0) {
            delt = min(abs(sum0), abs(sum1));
            ans += delt;
            sum0 += -sgn(sum0) * delt;
            sum1 += -sgn(sum1) * delt;
        }
        ans += abs(sum0) + abs(sum1);
    } cout << ans << endl;
} 



CF1685C

给定一个长度为 \(2n\) 的括号序列 \(s\),保证其由 \(n\) 个左括号 (\(n\) 个右括号 ) 组成。定义一次操作是翻转 \(s\) 的一个连续子串。请求出最少几次操作可将 \(s\) 转换为合法括号序列。输出最少的操作次数,并构造一种方案。可以证明,这总是能在 \(n\) 次操作中完成。

\(n \le 2\times 10^5\)

结论题。

注意这里的操作定义是这样的:设我们用 \(str\) 数组从下标 1 位置开始存储这个序列,则对 \([l,r]\) 序列的操作等价于 reverse(str+l,str+r+1)。无论如何操作,左括号和右括号的数量不会发生变化,因此 \(a_{2n}\) 恒等于 \(0\)

括号序列的转化是 (\(\to 1\))\(\to -1\),合法括号序列是任意位置非负。
设括号序列 \(s\) 的前缀和数组为 \(a_i\)。若 \(\forall i,a_i \ge 0\) 则不需要操作,此后讨论默认 \(s\) 不是合法括号序列。

我们断言,使用不超过两次操作就可以得到合法括号序列。

证明

我们只需要找到前缀和数组的最大值 \(a_k\),随后翻转 \([1,k]\)\([k+1,2n]\) 即可。

设翻转后的前缀和数组为 \(b_i\)
\(i \le k\) 时,\(b_i\) 对应的是原序列 \([i,k]\) 段的和,即 \(a_k - a_i\)。由于 \(a_k\) 最大,因此此情况下 \(b_i \ge 0\)
\(i > k\) 时,\(b_i\) 对应的是原序列 \([1,k]\) 段和 \([i,2n]\) 段的和,即 \(a_k + a_{2n} - a_{i'}\),其中 \(i\)\(i'\) 翻转后对应位置。由于 \(a_{2n} = 0\)\(a_k\) 最大,因此此情况下 \(b_i \ge 0\)

因此我们只需要判断是否能仅操作一次。
\([L,R]\) 分别为最前和最后一个 \(a\) 小于 \(0\) 的位置。我们断言,最终翻转的区间 \([l,r]\) 需要满足 \(l \le L \le \ R < r\),并且最优选择一定是 \(a_{l-1}\) 最大的 \(l\)\(a_r\) 最大的 r。不保证最优选择在操作后一定得到合法括号序列。

证明

可以知道最终 \([1,l)\)\((r,2n]\) 的前缀和没有变化。这表明最终翻转的区间 \([l,r]\) 需要满足 \(l \le L \le \ R < r\)
考察 \([l,r]\) 段新的前缀和 \(b_i\),立即列出方程:\(b_i = a_{l-1} + a_r - a_{i'}\),其中 \(i\)\(i'\) 翻转后对应位置。
则当 \(a_{l-1} + a_r - a_{i'} \ge 0\)\(a_{l-1} + a_r \ge a_{i'}\) 时有操作一次即可得到合法括号序列。
我们只需要贪心地选择最大值即可得到最优选择。

选择后只需要检验一次,若可行则输出 1,若不可行则输出 2。
构造方案平凡,上方已给出。

总时间复杂度 \(O(n)\)

code
#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for (register int (i) = (a); (i) <= (b); ++(i))
#define pre(i,a,b) for (register int (i) = (a); (i) >= (b); --(i))
const int N = 4e5 + 10;
int T, n, mpos, pref[N];
char ch[N];

bool check(int l, int r) {
    reverse(ch+l, ch+r+1);

    rep(i,l,r) {
        pref[i] = pref[i-1] + (ch[i] == '(' ? 1 : -1);
        if (pref[i] < 0) return false;
    } 
    rep(i,r+1,n) {
        pref[i] = pref[i-1] + (ch[i] == '(' ? 1 : -1);
        if (pref[i] < 0) return false;
    } 

    return true;
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> T;
    while (T--) {
        cin >> n >> ch + 1; n <<= 1; 
        bool completed = true; mpos = 1; pref[n+1] = -1e9;

        rep(i,1,n) {
            pref[i] = pref[i-1] + (ch[i] == '(' ? 1 : -1);
            if (pref[mpos] < pref[i]) mpos = i;
            if (pref[i] < 0) completed = false;
        } 
        if (completed) { cout << 0 << '\n'; continue; };

        int lmst = 1, rmst = n;
        while (lmst <= n and pref[lmst] >= 0) lmst ++;
        while (rmst > 0 and pref[rmst] >= 0) rmst --;

        int lck = n+2, rck = n+1;
        rep(i,1,lmst) if (pref[lck-1] < pref[i-1]) lck = i;
        rep(i,rmst+1,n) if (pref[rck] < pref[i]) rck = i;

        if (check(lck, rck)) {
            cout << 1 << '\n';
            cout << lck << ' ' << rck << '\n';
        }
        else {
            cout << 2 << '\n';
            cout << 1 << ' ' << mpos << '\n';
            cout << mpos+1 << ' ' << n << '\n';
        } 
    }
}
posted @ 2022-10-04 18:53  joke3579  阅读(88)  评论(7编辑  收藏  举报