260716 康复性训练记录

前言

I believe I can do my best.

即使在你看来已经到了绝境,也不要放弃。因为事实并非像你想象的那样。——马明生(2025,北京)

概况

08:35 开始打非模拟赛,12:00 结束。打了 T1 的 100 分,T2 的 100 分,T3 的 100 分,没有注意到 T5、6 的 200 分,最后以 300 分收尾。总结一下,不要把 T1 想的太难,以防 T4 写不完!
注意:难度估算参考洛谷七级难度+上下位,即:
入门——普及−——普及/提高-——普及+/提高——提高+/省选−——省选/NOI−——NOI/NOI+/CTSC

# A B C D E F
6 300 100 100 100 0 - -
T 03:00:00 01:49:01 01:59:45 02:53:28 03:51:23 99:99:99 99:99:99

A

题目描述

这是一种方格图,要求方格连续,且左边界对齐,下一行不能比上一行长。
现在我们可以往里面填 \(1-n\) 的数字,要求每个格子中的数字要大于等于左边格子,严格大于上方格子
现在给定行数 \(k\) 和每一行的格子数量 \(l_i\),求可能满足条件的图形数量。
如图所示为可能出现的几种积木情况。
图片

\(k\le n\le 7\)\(1\le l_k\le l_{k-1}\le\cdots\le l_1\le 7\)。不需要文件读写。

解题思路

不一定是最优解。
注意到,如果在一行 \(7\) 个格子中填入 \(1\sim7\),且要求各格数字大于等于左边的,则总方案数大约为 \(1700\)。若一行方格数 \(1\sim 7\) 任取则总方案数约为 \(3400\)。因此将每个填数方案视为点,在可以成为上下行的填数方案间连有向边(下指向上)。设 \(ans[i][j]\) 为第 \(i\) 行填入方案 \(j\) 的方案数,于是有 \(ans[i][u]=\sum\limits_{v\in son[u]} ans[i-1][v]\)。最后暴力枚举最后一行的方案就行了。时间复杂度 \(\mathcal{O}(k^n+km^2)\),其中 \(m\) 为一行 \(7\) 个格子中填入 \(1\sim n\),且要求各格数字大于等于左边的方案数。
可以给到上位黄。

#include <bits/stdc++.h>
using namespace std;
#define gc getchar
template<typename T> void read(T &x) {
    char c = gc(); int f = 1; x = 0;
    while (!isdigit(c)) f = (c == '-' ? -f : f), c = gc();
    while (isdigit(c)) x = (x << 3) + (x << 1) + c - '0', c = gc();
    x *= f;
}
template<typename T, typename ... Args> void read(T &x, Args& ... args) {read(x); read(args...);}
#define rep(i, s, t) for(int i=(s);i<=(t);i++)
#define per(i, s, t) for(int i=(s);i>=(t);i--)
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define lll __int128
template<typename T> void chkmx(T &x, const T &y) {x = max(x, y);}
template<typename T> void chkmn(T &x, const T &y) {x = min(x, y);}
const int N = 10, M = 3500;
ll k, l[N], n, cnt = 0;
ll a[M], b[N], c[N], ans[N][M];
vector<int> v[M];
vector<int> s;
void dfs(int p, int q, int now) {
	// s.push_back(q);
    a[cnt] = now; cnt++; // cerr << cnt - 1 << ": ";
	// for (int i = 1; i <= s.size(); i++) cerr << s[i - 1]; cerr << '\n';
	if (p == 7) {
		// s.pop_back();
		return;
	}
	for (int i = q; i <= n; i++) dfs(p + 1, i, now * 8 + i);
	// s.pop_back();
}
bool ok(int i, int j) {
    i = a[i]; j = a[j];
    rep(p, 0, 6) {b[p] = i % 8; i /= 8;}
    rep(p, 0, 6) {c[p] = j % 8; j /= 8;}
    // rep(p, 0, 6) cout << b[p] << ' ' << c[p] << '\n';
    for (int i = 6, j = 6; i >= 0 && j >= 0; ) {
        // cout << i << ' ' << j << ' ' << b[i] << ' ' << c[j] << '\n';
        if (b[i] == 0) {i--; continue;}
        if (c[j] == 0) {j--; continue;}
        if (i < j) return false;
        if (b[i] >= c[j]) return false;
        i--; j--;
    }
    return true;
}
bool check(int i, int len) {
    i = a[i];
    rep(p, 0, 7) {b[p] = i % 8; i /= 8;}
    return b[len - 1] != 0 && b[len] == 0;
}
int main() {
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    read(k); rep(i, 1, k) read(l[i]); read(n);
    rep(j, 1, n) dfs(1, j, j);
    // cout << cnt << '\n'; return 0;
    // cout << ok(293, 1003) << '\n'; 
    // cout << ok(45, 3002) << '\n'; 
    rep(i, 0, cnt - 1) {
        rep(j, 0, cnt - 1) {
            if (ok(i, j)) v[j].push_back(i);
        }
    }
    rep(i, 0, cnt - 1) {
        ans[1][i] = check(i, l[1]);
    }
    rep(i, 2, k) rep(s, 0, cnt - 1) {
        if (check(s, l[i])) {
            rep(j, 0, (int)v[s].size() - 1) {
                // cerr << i << ' ' << s << ' ' << j << ' ' << v[s][j];
                ans[i][s] += ans[i - 1][v[s][j]];
            }
        }
    }
    ll finalans = 0;
    rep(i, 0, cnt - 1) {
        if (check(i, l[k])) finalans += ans[k][i];
    }
    cout << finalans << '\n';
    return 0;
}

B

题目描述

\(n\) 个数 \(a_i\)。状态可以从 \(i\) 变成 \(j\) ,当且仅当 \(a_i<a_j\)\(|i-j|\le w\)
可以选择数作为初始状态,请你求出最多可以变换多少次状态。

\(1\le w\le n\le 5000\)\(1\le a_i\le 10^9\)。读写文件名 bright.in/out

解题思路

建个树跑 DFS 求高度。太简单不讲。给一个橙。

#include <bits/stdc++.h>
using namespace std;
#define gc getchar
template<typename T> void read(T &x) {
    char c = gc(); int f = 1; x = 0;
    while (!isdigit(c)) f = (c == '-' ? -f : f), c = gc();
    while (isdigit(c)) x = (x << 3) + (x << 1) + c - '0', c = gc();
    x *= f;
}
template<typename T, typename ... Args> void read(T &x, Args& ... args) {read(x); read(args...);}
#define rep(i, s, t) for(int i=(s);i<=(t);i++)
#define per(i, s, t) for(int i=(s);i>=(t);i--)
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define lll __int128
template<typename T> void chkmx(T &x, const T &y) {x = max(x, y);}
template<typename T> void chkmn(T &x, const T &y) {x = min(x, y);}
const int N = 5005;
int n, w, a[N], dep[N];
vector<int> v[N];
void dfs(int p) {
    dep[p] = 1;
    for (int i = 0; i < v[p].size(); i++) {
        int q = v[p][i];
        if (!dep[q]) dfs(q);
        dep[p] = max(dep[p], dep[q] + 1);
    }
}
void pd() {
    read(n, w); rep(i, 1, n) read(a[i]);
    rep(i, 1, n) {
        rep(j, max(1, i - w), min(n, i + w)) if (a[i] < a[j]) v[i].push_back(j);
    }
    rep(i, 1, n) if (!dep[i]) dfs(i);
    int ans = 0;
    rep(i, 1, n) ans = max(ans, dep[i]);
    cout << ans - 1 << '\n';
    return;
}
int main() {
    freopen("bright.in", "r", stdin);
    freopen("bright.out", "w", stdout);
    // int _; read(_); while (_--)
    pd();
    return 0;
}

C

题目描述

给定长为 \(n\),数字为 \(1\sim k\) 的序列 \(x_i\),要选择一个子序列,满足是 \(k\) 的排列且字典序最小。

\(1\le k\le n\le 2\times10^5\)。读写文件名 photo.in/out

解题思路

算是单调栈里比较典型的题了。但我考试时不知道/忘了 : (。

尝试 DP 发现有后效性。于是开始贪心。

一种容易想到的贪心:假设已经处理完毕前 \(i-1\) 个数。考虑如果第 \(i\) 个数没有在已选数字中,则选择它;否则判断保持已选序列不变,和取消已选序列中等于 \(a_i\) 的数并改选 \(a_i\) 的字典序,并选择更小的那个,因为这样一定不劣。但这么做会被形如 \(2,3,1,2,3\) 的数据卡掉。

可以发现,上面的做法错误是因为判断是否用靠后数字换掉靠前数字时会收到已选数字的干扰,而已选数字不一定是最优的。因此直接贪心不行,需要借助其他技巧维护。并非不难发现,如果用单调栈维护已选数列,则可以选出字典序最小的子序列,但可能有一部分数字没有进入单调栈。因此在维护单调栈时,当栈内数字将要弹出时,如果后面这个数字不可能再被加入单调栈,则不弹出这个数字。容易证明这个做法是对的,时间复杂度 \(\mathcal{O}(n)\)。难度大概黄/绿?感觉上位黄可能性更大一点。

#include <bits/stdc++.h>
using namespace std;
#define gc getchar
template<typename T> void read(T &x) {
    char c = gc(); int f = 1; x = 0;
    while (!isdigit(c)) f = (c == '-' ? -f : f), c = gc();
    while (isdigit(c)) x = (x << 3) + (x << 1) + c - '0', c = gc();
    x *= f;
}
template<typename T, typename ... Args> void read(T &x, Args& ... args) {read(x); read(args...);}
#define rep(i, s, t) for(int i=(s);i<=(t);i++)
#define per(i, s, t) for(int i=(s);i>=(t);i--)
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define lll __int128
template<typename T> void chkmx(T &x, const T &y) {x = max(x, y);}
template<typename T> void chkmn(T &x, const T &y) {x = min(x, y);}
const int N = 2e5 + 5;
int n, k, x[N];
int lstp[N];
bool ins[N];
vector<int> v;
int main() {
    freopen("photo.in", "r", stdin);
    freopen("photo.out", "w", stdout);
    read(n, k); rep(i, 1, n) {
        read(x[i]);
        lstp[x[i]] = i;
    }
    rep(i, 1, n) {
        if (ins[x[i]]) {
            continue;
        }
        while (v.size()) {
            int top = v[v.size() - 1];
            if (top > x[i] && lstp[top] > i) {
                v.pop_back();
                ins[top] = false;
            } else break;
        }
        ins[x[i]] = true;
        v.push_back(x[i]);
    }
    rep(i, 0, k - 1) cout << v[i] << ' '; cout << '\n';
    return 0;
}

D

题目描述

\(n\) 个数组成一个数组,下标为 \(1\)\(n\) 。现在有 \(q\) 个询问,每次询问如果交换数组中的第 \(a\) 个数和第 \(b\) 个数,那么数组中有多少逆序对。询问不会真的交换两个数字。

\(1\le k\le n\le 2\times10^5\)\(1\le a_i\le 10^9\)。读写文件名 inversions.in/out

解题思路

这道题我好像做过,至于是不是真的做过我也不知道,忘了。

可以发现,对调后真正受影响的是:其中一个数或两个数是第 \(a\) 个或第 \(b\) 个数的顺、逆序对。因此不难发现,对于此题可以用某种神奇的数据结构维护区间内大于、等于、小于数组中第 \(i\) 个数的数的个数,以便统计出对调后产生和消失的逆序对数。这让我们想到询问离线+树状数组的方法。

简单来说,每个询问的格式是:查询区间 \([L,R]\) 内小于/小于等于/等于/大于等于/大于数字 \(x\) 的数个数。将询问按 \(x\) 排序后,将数组中的数字从小到大添加到值域树状数组里(此时树状数组维护数字大小在 \([l,r]\) 内数字的个数)。每添加一个数 \(x\),就处理所有询问数字 \(x\) 的询问。

时间复杂度 \(\mathcal{O}(n\log n+q\log q)\)。可以给到绿。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
const int N = 4e5 + 5;
long long ans = 0;
long long n, q;
long long a[N], b[N], c[N], ls[N];
void merge(int l, int r) {
    int p1 = l, mid = (l + r) >> 1, p2 = mid + 1, p = l;
    while (p1 <= mid && p2 <= r) {
        if (a[p1] <= a[p2]) {
            b[p++] = a[p1++];
        } else {
            b[p++] = a[p2++];
            ans += mid - p1 + 1;
        }
    }
    while (p1 <= mid) {
        b[p++] = a[p1++];
    }
    while (p2 <= r) {
        b[p++] = a[p2++];
    }
    for (int i = l; i <= r; i++) {
        a[i] = b[i];
    }
}
void merge_sort(int l, int r) {
    if (l == r) {
        return;
    }
    int mid = (l + r) / 2;
    merge_sort(l, mid);
    merge_sort(mid + 1, r);
    merge(l, r);
}
struct node {
    int l, r, x, id;
} qu[2 * N];
int cnt = 0;
bool cmp(node x, node y) {return x.x < y.x;}
inline int lowbit(int x) {return x & -x;}
int s[N];
int sum(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) {
        res += s[i];
    }
    return res;
}
void add(int x, int k) {
    for (int i = x; i <= n; i += lowbit(i)) {
        s[i] += k;
    }
}
int query(int l, int r) {    return sum(r) - sum(l - 1);}
struct nod {
    int x, id;
} d[N];
bool cmp1(nod x, nod y) {return x.x < y.x;}
int num[N * 2], L[N], R[N];
signed main() {
    freopen("inversions.in", "r", stdin);
    freopen("inversions.out", "w", stdout);
    scanf("%lld%lld", &n, &q);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", a + i);
        ls[i] = c[i] = a[i];
    }
    sort(ls + 1, ls + n + 1);
    int tot = unique(ls + 1, ls + n + 1) - ls - 1;
    for (int i = 1; i <= n; i++) {
        int x = lower_bound(ls + 1, ls + tot + 1, c[i]) - ls;
        a[i] = c[i] = x;
        d[i] = {x, i};
    }
    sort(d + 1, d + n + 1, cmp1);
    merge_sort(1, n);
    for (int i = 1; i <= q; i++) {
        int l, r; cin >> l >> r;
        if (l > r) swap(l, r);
        L[i] = l; R[i] = r;
        int vL = c[l], vR = c[r];
        int len = r - l - 1;
        int base = (i - 1) * 4;
        if (vL > 1) qu[++cnt] = {l + 1, r - 1, vL - 1, base + 0};
        else num[base + 0] = 0;
        qu[++cnt] = {l + 1, r - 1, vL, base + 1};
        if (vR > 1) qu[++cnt] = {l + 1, r - 1, vR - 1, base + 2};
        else num[base + 2] = 0;
        qu[++cnt] = {l + 1, r - 1, vR, base + 3};
    }
    sort(qu + 1, qu + cnt + 1, cmp);
    for (int i = 1, j = 1, k = 1; i <= tot; i++) {
        while (j <= n && d[j].x == i) add(d[j++].id, 1);
        while (k <= cnt && qu[k].x == i) {
            num[qu[k].id] = query(qu[k].l, qu[k].r);
            k++;
        }
    }
    for (int i = 1; i <= q; i++) {
        int base = (i - 1) * 4;
        int lL = num[base + 0];
        int lqL  = num[base + 1];
        int lR = num[base + 2];
        int lqR  = num[base + 3];
        int vL = c[L[i]], vR = c[R[i]];
        printf("%lld\n", ans + lR - lqL - lL + lqR + ((vL < vR) ? 1 : ((vL > vR) ? -1 : 0)));
    }
    return 0;
}

E

题目描述

一个长度为 \(n\) 的数组,数组元素均为整数,每一个整数可以变成一个 A、B 或 C,但同种数字只能变成同一个字符,问你有多少种不同的变法使得产生的字符串的所有子序列中包含至少一个 ABC,由于答案可能很大,请将答案对 \(10^8+7\) 取模。
\(n\le 3000\)。读写文件名 abc.in/out

解题思路

还没学会。

F

题目描述

有一个 \(2×M\) 的棋盘,将棋盘染成三种颜色红、绿、蓝。一个棋盘是漂亮的,当且仅当:

  • 任意两个相邻的格子颜色不同;
  • 任意一个 \(2\times2\) 的网格里,每一种颜色都至少出现了一次。

求恰有 \(R\) 个红色格子、\(G\) 个绿色格子和 \(B\) 个蓝色格子的漂亮的棋盘的数量模 \(10^9+7\)

\(M\le 10^7\)。读写文件名 board.in/out

解题思路

显然 \(R+G+B=2M\)

将棋盘每一列看做一个 有序对\((color_1,color_2)\),且 \(color_1\not=color_2\)。由同样颜色不能相同,且每 \(2\times 2\) 个方格有 \(3\) 种颜色得:每列的无序对不能相同。当确认每列的无序对时,只要规定其中一个无序对的顺序,其他无序对的顺序就存在且唯一,因此这道题目的方案数 \(=\) 无序对方案数 \(\times 2\)

对于无序对 \((red,green)\)\((green,blue)\)\((red,blue)\),设要求的个数分别为恰好 \(a,b,c\),则有 \(R=a+c,G=a+b,B=b+c\)。解得 \(a=\frac{R+G−B}2,b=\frac{R+B−G}2,c=\frac{G+B−R}2\)

于是问题转化为:求相邻元素不同,且恰有 \(a\) 个 A,\(b\) 个 B,\(c\) 个 C 的序列数。这个问题可以容斥解决。

具体的,有

\[\sum_{i=0}^{a-1}\sum_{j=0}^{b-1}\sum_{k=0}^{c-1} (-1)^{i+j+k} \binom{a-1}{i}\binom{b-1}{j}\binom{c-1}{k} \frac{(n-i-j-k)!}{(a-i)!(b-j)!(c-k)!} \]

。但是这个算式还是太不好算了。至于怎么优化,以后开一个结题报告专门讲一下。难度给到紫。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll N = 1e7 + 5, mod = 1e9 + 7;
ll red, green, blue, m, a, b, c, fac[N], invf[N], pow2[N];
ll fpow(ll x, ll y) {
    if (y == 1) return x;
    ll res = 1;
    while (y) {
        if (y & 1) res = res * x % mod;
        y >>= 1;
        x = x * x % mod;
    }
    return res;
}
ll comb(ll x, ll y) {return (ll)fac[x] * invf[y] % mod * invf[x - y] % mod;}
ll C(ll k, ll p, ll q) {
    if (b - k - p < 0 || b - k - p >= a || c - k - q < 0 || c - k - q >= a || b + c - k - k - p - q < a - 1) return 0ll;
    ll s = b + c - k - k - p - q, i = s - a + 1, j = a - 1 - c + k + q, l = a - 1 - b + k + p;
    return pow2[i] * fac[a - 1] % mod * invf[i] % mod * invf[j] % mod * invf[l] % mod; 
}
int main() {
    freopen("board.in", "r", stdin);
    freopen("board.out", "w", stdout);
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin >> m >> red >> green >> blue;
    if ((red + green - blue) % 2 || (red + blue - green) % 2 || (green + blue - red) % 2) {puts("0"); return 0;}
    a = (red + green - blue) / 2, b = (red + blue - green) / 2, c = (green + blue - red) / 2;
    if (a < 0 || b < 0 || c < 0) {puts("0"); return 0;}
    if (b < c) swap(b, c);
    if (a < b) swap(a, b);
    if (b < c) swap(b, c);
    if (a > b + c + 1) {puts("0"); return 0;}
    fac[0] = pow2[0] = 1;
    for (int i = 1; i <= m; i++) fac[i] = (ll)fac[i - 1] * i % mod, pow2[i] = (ll)pow2[i - 1] * 2 % mod;
    invf[m] = fpow(fac[m], mod - 2);
    for (int i = m - 1; i >= 1; i--) invf[i] = (ll)invf[i + 1] * (i + 1) % mod;
    invf[0] = 1;
    ll ans = 0;
    ll C2[3] = {1, 2, 1};
    for (int k = 0; k <= min(b, c); k++) {
        ll tmp = 0;
        for (int p = 0; p <= 2; p++) {
            for (int q = 0; q <= 2; q++) {
                tmp = (tmp + C2[p] * C2[q] % mod * C(k, p, q)) % mod;
            }
        }
        ans = (ans + comb(a + k, k) * tmp) % mod;
    }
    cout << 2ll * ans % mod << '\n';
    return 0;
}
posted @ 2026-07-30 16:56  cwkapn  阅读(8)  评论(0)    收藏  举报