CF & AT 做题记录
给定一个
个数的序列 。你可以进行若干次操作,每次操作可以在一个数 上画圈。
你需要满足以下要求:设你的操作次数为, 为你每次操作时画圈的 下标,你需要满足没有画圈的数字个数 恰好为 ,且这些数字组成的子序列 与 相等。
请你构造一组方案。如果不存在合法方案,输出-1
。
。
一开始就在做,比赛唯一场切的题(
方向很清晰,就是细节有点多(?)
如果一个数
从没有被圈的数入手,记这种数为
既然是基环树,首先要找出环。从底向上推,叶子必须是
接下来开始分讨。注意到两个环上
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> #include <vector> using namespace std; // #define DeBug // #define LOCAL // #define TestCases const int N = 2e5; int n; int a[N + 5]; vector<int> e[N + 5]; int notuse[N + 5]; int stk[N + 5], top; int vis[N + 5]; int seq[N + N + 5], len; int oncir[N + 5]; void findcir(int u) { if (vis[u]) { len = 0; while (stk[top] != u) { seq[++len] = stk[top]; top--; } seq[++len] = u; reverse(seq + 1, seq + len + 1); top--; for (int i = 1; i <= len; i++) { oncir[seq[i]] = 1; seq[i + len] = seq[i]; } return ; } stk[++top] = u; vis[u] = 1; findcir(a[u]); return ; } void dfs(int u) { vis[u] = 1; if (e[u].empty()) { notuse[u] = 1; return ; } int zero = 0, one = 0, son = 0; for (unsigned int i = 0; i < e[u].size(); i++) { int v = e[u][i]; if (oncir[v]) continue; son++; dfs(v); if (notuse[v] == 0) zero++; if (notuse[v] == 1) one++; } if (zero == son) { if (oncir[u]) notuse[u] = 2; else notuse[u] = 1; return ; } notuse[u] = 0; return ; } void calc(int st) { top = 0; findcir(st); for (int i = 1; i <= len; i++) dfs(seq[i]); int zero = 0; for (int i = 1; i <= len; i++) { if (notuse[seq[i]] == 0) { zero = i; break; } } if (zero) { for (int i = zero + 1; i < zero + len; i++) { if (notuse[seq[i]] == 0) continue; notuse[seq[i]] = (notuse[seq[i - 1]] ^ 1); } } else { if (len & 1) { puts("-1"); exit(0); } for (int i = 1; i <= len; i++) notuse[seq[i]] = (i & 1); } return ; } void solve() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", a + i); e[a[i]].push_back(i); notuse[i] = -1; } for (int i = 1; i <= n; i++) if (!vis[i]) calc(i); int cnt = 0; for (int i = 1; i <= n; i++) cnt += notuse[i]; printf("%d\n", cnt); for (int i = 1; i <= n; i++) if (notuse[i]) printf("%d ", a[i]); printf("\n"); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
给定一个长为
的序列 ,你需要对这个序列进行红蓝染色。染色有如下要求:
- 每个位置恰好染上其中一种颜色。
- 对于所有的值
,在这个序列的任意子区间 中,值为 且为红色的位置数 减去 值为 且为蓝色的位置数 的绝对值不超过 1。 - 如果按照位置顺序将所有红色的数排成序列
,蓝色的数排成序列 ,那么 的字典序必须 严格大于 的字典序。 统计所有的合法染色方案数。对
取模。
。
对于同一种数值,要求
要求字典序严格小于,一般会考虑 LCP ,但是这里有点诈骗的意味。如果一种方案红色的字典序大于蓝色的字典序,将所有数红蓝反转即可。设有
考虑一种数值,一定是第
排除以上两种情况,所有线段要么相离,要么相交。相交线段限制了一些位置必须同色,它们把序列切成
注意,并查集连通块数等于
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> #include <vector> using namespace std; // #define Debug // #define LOCAL const int N = 2e5; const long long P = 998244353, Inv = (P + 1) >> 1; int n; int a[N + 5]; int b[N + 5], m; vector<int> pos[N + 5]; int match[N + 5]; long long ksm(long long d, long long u) { long long res = 1; while (u) { if (u & 1) res = res * d % P; u >>= 1; d = d * d % P; } return res; } int fa[N + 5]; int find(int x) { if (fa[x] == x) return x; return fa[x] = find(fa[x]); } void connect(int x, int y) { x = find(x), y = find(y); if (x != y) fa[x] = y; return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", a + i); b[i] = a[i]; } m = n; sort(b + 1, b + m + 1); m = unique(b + 1, b + m + 1) - b - 1; for (int i = 1; i <= n; i++) { a[i] = lower_bound(b + 1, b + m + 1, a[i]) - b; pos[a[i]].push_back(i); } long long ans = ksm(2, m); for (int t = 1; t <= m; t++) { if (pos[t].size() & 1) { ans = ans * Inv % P; printf("%lld\n", ans); return 0; } int sz = pos[t].size(); for (int i = 0; i < sz; i += 2) { int u = pos[t][i], v = pos[t][i + 1]; match[u] = v, match[v] = u; } } for (int i = 1, mx = 0; i <= n; i++) { mx = max(mx, match[i]); if (match[i] > i && mx > match[i])//[i, mxt_i] \in [j, mxt_j] { ans = ans * Inv % P; printf("%lld\n", ans); return 0; } } int cnt = 0; for (int i = 1; i <= n; i++) fa[i] = i; for (int i = 1, lst = 0; i <= n; i++) { int t = a[i]; if (match[i] < i) { connect(pos[t][1], i); continue; } connect(pos[t][0], i); if (i < match[lst] && match[lst] < match[i]) { connect(i, lst); connect(match[i], match[lst]); } lst = i; } for (int i = 1; i <= n; i++) cnt += (find(i) == i); cnt /= 2; ans = (ans - ksm(2, cnt) + P) % P * Inv % P; printf("%lld\n", ans); return 0; }
给你一棵
个点的树,其中有一些边方向给定,剩下的边由你来决定方向。同时你要给每条边染一个颜色。
现在有一个人在树上走,他有一个栈,当他经过一条边时会进行如下操作:
- 如果他走的方向与边的方向相同,往栈里放一个与该边颜色相同的球。
- 如果他走的方向与边的方向相反,从栈顶取出一个球丢掉。
一个路径合法当且仅当每次走相反的边之前栈都不为空。
你构造的方案要满足对于任意合法路径,每次走反向边时取出的球都恰好与该边颜色相同。在此基础上使边的颜色数最多,并输出方案,无解输出-1
。
会不了一点
先 从特殊情况入手 ,比如 所有边都没有定向 。任选一个点作根,建一棵外向树,这样所有 Stackable 的路径都不会删数,共
如果边初始有方向呢?这时有一些边是
有一个小问题,如果某一时刻栈为空还要 pop 怎么办?此时
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; // #define Debug // #define LOCAL const int N = 1e5, E = N << 1; int n; int head[N + 5], to[E + 5], nxt[E + 5], tot = 1; int dir[E + 5]; void add_edge(int u, int v, int d) { tot++; to[tot] = v; nxt[tot] = head[u]; dir[tot] = d; head[u] = tot; return ; } void add(int u, int v, int d)//1: u -> v, 0: v -> u, 2/3: undir { add_edge(u, v, d); add_edge(v, u, d ^ 1); return ; } int dp[N + 5]; void dfs1(int u, int fa) { for (int i = head[u]; i; i = nxt[i]) { int v = to[i]; if (v == fa) continue; dfs1(v, u); dp[u] += dp[v] + (dir[i] == 0); } return ; } void dfs2(int u, int fa) { for (int i = head[u]; i; i = nxt[i]) { int v = to[i]; if (v == fa) continue; int other = dp[u] - dp[v] - (dir[i] == 0); dp[v] += other + (dir[i] == 1); dfs2(v, u); } return ; } int stk[N + 5], top; int cnt; void colour(int u, int fa) { for (int i = head[u]; i; i = nxt[i]) { int v = to[i]; if (v == fa) continue; if (dir[i] > 1) dir[i] = 1; if (dir[i] == 1) { cnt++; stk[++top] = cnt; printf("%d %d %d\n", u, v, cnt); colour(v, u); top--; } else { int ori = stk[top]; printf("%d %d %d\n", v, u, stk[top--]); colour(v, u); stk[++top] = ori; } } return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif scanf("%d", &n); for (int i = 1, u, v, d; i < n; i++) { scanf("%d%d%d", &u, &v, &d); if (d == 0) add(u, v, 2); else add(u, v, 1); } dfs1(1, 0); dfs2(1, 0); int rt = 0; for (int i = 1; i <= n; i++) { if (!rt || dp[rt] > dp[i]) rt = i; } printf("%d\n", n - 1 - dp[rt]); colour(rt, 0); return 0; }
给定一个长度为
的序列 以及正整数 ,要求从 中选出两个不相交的子序列,满足以下条件:
- 第一个子序列包含的元素依次递增
。 - 第二个子序列包含的元素依次递降
。 - 两个子序列总长为
。 - 两个子序列的平均值相同。
- 设两个子序列包含的下标最大值为
,最小值为 ,要求 最小。 求上述
的最小值,无解则输出 。
参(搬)考(运)了官方题解
从 平均值相同 入手。注意到每个序列都是连续递增/递减序列,所以
设
注意
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; // #define Debug // #define LOCAL // #define TestCases const int N = 2e5, Lg = 18; int n, k; int a[N + 5]; int nxt[N + 5]; int up[N + 5][2], down[N + 5][2];//0: prefix, 1: suffix int occ[N + 5]; int jmp[N + 5][Lg + 5][2][2];//u/d, fr/bk int getval(int u, int ud, int fb, int tar) { if (fb == 0) { for (int k = Lg; k >= 0; k--) { if (jmp[u][k][ud][fb] >= tar) u = jmp[u][k][ud][fb]; } } else { for (int k = Lg; k >= 0; k--) { if (jmp[u][k][ud][fb] <= tar) u = jmp[u][k][ud][fb]; } } return u; } int ans = N + 1; void update(int l, int r) { if (l != -1 && r != -1) ans = min(ans, r - l); return ; } void solve() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) scanf("%d", a + i); if (k & 1) return puts("-1"), void(); for (int i = 1; i <= n; i++) { up[i][0] = occ[a[i] + 1], down[i][0] = occ[a[i] - 1]; occ[a[i]] = i; } for (int i = 0; i <= n + 1; i++) occ[i] = n + 1; for (int i = n; i > 0; i--) { nxt[i] = occ[a[i]]; up[i][1] = occ[a[i] + 1], down[i][1] = occ[a[i] - 1]; occ[a[i]] = i; } up[0][0] = down[0][0] = 0, up[0][1] = down[0][1] = n + 1; up[n + 1][0] = down[n + 1][0] = 0, up[n + 1][1] = down[n + 1][1] = n + 1; for (int i = 0; i <= n + 1; i++) { jmp[i][0][0][0] = up[i][0], jmp[i][0][0][1] = up[i][1]; jmp[i][0][1][0] = down[i][0], jmp[i][0][1][1] = down[i][1]; } for (int k = 1; k <= Lg; k++) { for (int i = 0; i <= n + 1; i++) { jmp[i][k][0][0] = jmp[ jmp[i][k - 1][0][0] ][k - 1][0][0]; jmp[i][k][1][0] = jmp[ jmp[i][k - 1][1][0] ][k - 1][1][0]; } for (int i = n + 1; i >= 0; i--) { jmp[i][k][0][1] = jmp[ jmp[i][k - 1][0][1] ][k - 1][0][1]; jmp[i][k][1][1] = jmp[ jmp[i][k - 1][1][1] ][k - 1][1][1]; } } for (int i = 1; i <= n; i++) { int u = i, v = nxt[i], L = -1, R = -1; if (nxt[i] > n) continue; int l = 1, r = u; while (l <= r) { int mid = (l + r) >> 1; int upper = getval(u, 0, 0, mid), lower = getval(u, 1, 0, mid); if (a[upper] - a[lower] + 1 >= k / 2) L = mid, l = mid + 1; else r = mid - 1; } l = v, r = n; while (l <= r) { int mid = (l + r) >> 1; int upper = getval(u, 0, 1, mid), lower = getval(v, 1, 1, mid); if (a[upper] - a[lower] + 1 >= k / 2) R = mid, r = mid - 1; else l = mid + 1; } update(L, R); l = v, r = n, R = -1; while (l <= r) { int mid = (l + r) >> 1; int upper = getval(v, 0, 1, mid), lower = getval(u, 1, 1, mid); if (a[upper] - a[lower] + 1 >= k / 2) R = mid, r = mid - 1; else l = mid + 1; } update(L, R); /*==================================================================*/ l = v, r = n, R = -1; while (l <= r) { int mid = (l + r) >> 1; int upper = getval(v, 0, 1, mid), lower = getval(v, 1, 1, mid); if (a[upper] - a[lower] + 1 >= k / 2) R = mid, r = mid - 1; else l = mid + 1; } l = 1, r = u, L = -1; while (l <= r) { int mid = (l + r) >> 1; int upper = getval(v, 0, 0, mid), lower = getval(u, 1, 0, mid); if (a[upper] - a[lower] + 1 >= k / 2) L = mid, l = mid + 1; else r = mid - 1; } update(L, R); l = 1, r = u, L = -1; while (l <= r) { int mid = (l + r) >> 1; int upper = getval(u, 0, 0, mid), lower = getval(v, 1, 0, mid); if (a[upper] - a[lower] + 1 >= k / 2) L = mid, l = mid + 1; else r = mid - 1; } update(L, R); } if (ans > n) ans = -1; printf("%d\n", ans); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
Clubstep 是 Chaneka 喜爱的电子游戏中最难的之一。Clubstep 中包含
关,从 到 编号。Chaneka 已经对这个游戏进行了大量练习,所以目前她对其中的第 关的熟悉度是 。
之后,Chaneka 可以在 Clubstep 上进行若干次(可以是 0 次)尝试。在每一次尝试中她会在关中的某一关挂掉。某次尝试在第 关挂掉意味着她通过了 中的每一关,且没有进入 中的任何一关。一次挂在第 关的尝试用时 秒。
我们知道 Chaneka 在自己挂掉的那一关上会比其他任何一关进步更多。我们也知道在一次尝试中 Chaneka 在自己没有进入的关卡无法取得进步。所以一次挂在第关的尝试的影响如下:
- Chaneka 对第
关的熟悉度增加 。 - Chaneka 对
中的每一关的熟悉度增加 。 现在有
个询问,第 个询问给定三个正整数 。你需要求出 Chaneka 通过若干次尝试后对 中的每一关熟悉度都不小于 的最小用时(以秒为单位)。注意每次询问都是独立的,所以 Chaneka 在某次询问中的尝试不会影响其他任何一次询问的熟悉度。
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> #include <vector> #include <queue> using namespace std; // #define Debug // #define LOCAL // #define TestCases const int N = 3e5, SZ = N * 40; int n, q; int a[N + 5]; long long ans[N + 5]; typedef pair<int, int> pir; vector<pir> add[N + 5]; vector<int> del[N + 5]; int fa[SZ + 5], tot; long long val[SZ + 5]; int find(int x) { if (fa[x] == x) return x; int parent = fa[x]; fa[x] = find(fa[x]); val[x] += val[parent]; return fa[x]; } priority_queue<pir> que; vector<pir> tmp; void solve() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i); scanf("%d", &q); for (int i = 1, l, r, x; i <= q; i++) { scanf("%d%d%d", &l, &r, &x); add[r].emplace_back(x, i); del[l].push_back(i); } tot = q; for (int i = 1; i <= tot; i++) fa[i] = i; for (int i = n, x, id; i > 0; i--) { for (auto v : add[i]) que.push(v); tmp.clear(); while (!que.empty()) { x = que.top().first; id = que.top().second; if (x <= a[i]) break; que.pop(); int tim = (x - a[i] + 1) >> 1; val[id] += 1ll * tim * i; x -= tim; tmp.emplace_back(x, id); } int sz = tmp.size(); for (int l = 0, r = 0; l < sz; l = r + 1) { r = l; while (r + 1 < sz && tmp[r].first == tmp[r + 1].first) r++; tot++; fa[tot] = tot; for (int t = l; t <= r; t++) fa[tmp[t].second] = tot; que.emplace(tmp[l].first, tot); } for (auto id : del[i]) { find(id); ans[id] = val[id]; } } for (int i = 1; i <= q; i++) printf("%lld\n", ans[i]); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
给一个
行 列的网格,那么它的所有网格线上共有 条竖边, 条横边。
有如下两种操作:
- 选一个上面和左面的网格线都没被涂黑的格子,并涂黑这两条线;
- 选一个下面和右面的网格线都没被涂黑的格子,并涂黑这两条线。
求执行两种操作若干次(可以为
),可能得到不同的涂黑边集数量。
。
不会啊
关键在与 把网格图划分为若干独立的部分 !!!
贺一张题解的图:
合法的染色方案满足每一段黑色连续段的长度都是偶数。考虑 dp :
易知这是斐波那契数列,但是直接递推就行了
不妨设
前面快速幂,后面预处理
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; // #define Debug // #define LOCAL const int N = 2e6; const long long P = 998244353; long long f[N + 5], g[N + 5]; long long ksm(long long d, long long u) { long long res = 1; while (u) { if (u & 1) res = res * d % P; u >>= 1; d = d * d % P; } return res; } void solve() { int n, m; scanf("%d%d", &n, &m); if (n > m) swap(n, m); long long ans = ksm(f[n + n + 1], m - n); ans = ans * g[n + n] % P; printf("%lld\n", ans); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif f[0] = 1, f[1] = 1; for (int i = 2; i <= N; i++) f[i] = (f[i - 1] + f[i - 2]) % P; g[0] = 1; for (int i = 2; i <= N; i += 2) g[i] = g[i - 2] * f[i] % P * f[i] % P; int T; scanf("%d", &T); while (T--) solve(); return 0; }
给定正整数
和一个长度为 的序列 。对于一个 的排列 ,我们定义 为以下问题的答案:
- 给一个
个点的无向带权图,对于两点 ,当且仅当 时,它们之间有边,边权为 。求这个图的最小生成树边权和。 对于所有可能的排列
,求出它们的 之和,答案对 取模。
, 。
怎么
依然考虑算贡献,不妨把
从最小生成树算法入手 ,本题采用经典的 Kruskal. 我们想要求出所有排列中第
接下来求
对于
综上,
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; // #define Debug // #define LOCAL // #define TestCases const int N = 5000; const long long P = 998244353; int n, k; long long a[N + 5]; long long fac[N + 5], C[N + 5][N + 5]; void init() { fac[0] = 1; for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % P; C[0][0] = 1; for (int i = 1; i <= n; i++) { C[i][0] = 1; for (int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % P; } return ; } long long f[N + 5], g[N + 5]; void solve() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) scanf("%lld", a + i); sort(a + 1, a + n + 1); init(); for (int i = 1; i <= n; i++) { for (int j = 1; j <= k; j++) g[i] = (g[i] + C[n - j][i - 1]) % P; g[i] = g[i] * (i - 1) % P; } for (int i = 1; i <= n; i++) f[i] = g[i] * fac[i] % P * fac[n - i] % P; long long ans = 0; for (int i = 2; i <= n; i++) { long long add = a[i] * (f[i] - f[i - 1] + P) % P; ans = (ans + add) % P; } printf("%lld\n", ans); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
多组测试,每次给定一个
,你需要构造一个三角形满足以下条件。
- 该三角形的三个顶点都为格点
- 该三角形的面积为
- 该三角形内恰好只包含一个边长为1的正方形且该正方形的顶点也为格点(正方形的边可以和三角形的边或顶点重合)
场上以为是阴间数论就弃了,赛后发现是构造,但只会一半
有人说是打表找规律,但打完表也没看出什么
分类讨论,可以把三角形平移使原点成为一个顶点
打表可知
如图,以
设
考虑再固定一个点,根据直觉和样例,三角形大概是倾斜放置的,即没有边与坐标轴平行,不妨令
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; // #define Debug // #define LOCAL #define TestCases void solve() { int s; scanf("%d", &s); if (s & 1) { if (s < 9) puts("No"); else printf("Yes\n0 0 3 1 %d %d\n", (s - 3) / 2, (s - 1) / 2); } else { if (s == 2) puts("No"); else printf("Yes\n0 0 0 %d 2 %d\n", s / 2, (s / 2 - 1) * 2); } return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
对于每组数据,给定
, 与数组 的第 到 项和数组 的第 到 项。你需要根据 数组求出 个 数组的值,具体地:
对于每一个独立的
数组与互不影响的 ,你可以将 、 数组中的数字随意排序,再随意删除 与 中的 个数,对于每一个 数组,求最小的 使得 ,输出所有 的删除数 的和
赛时看见了但没注意到可以重排序列,以为是什么高妙匹配(
先考虑
推广上述做法,猜想当
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; // #define DeBug // #define LOCAL #define TestCases const int N = 1e5; int n, m; int a[N + 5], b[N + 5], tmp[N + 5]; int calc(int x) { tmp[1] = x; for (int i = 2; i <= n; i++) tmp[i] = a[i]; sort(tmp + 1, tmp + n + 1); for (int i = 1, pos = 1; i <= n; i++) { while (b[pos] <= tmp[i]) pos++; if (pos > n) return n - (i - 1); pos++;//a_i(tmp_i) match b_pos, pos should +1 } return 0; } void solve() { scanf("%d%d", &n, &m); for (int i = 2; i <= n; i++) scanf("%d", a + i); for (int i = 1; i <= n; i++) scanf("%d", b + i); sort(b + 1, b + n + 1); b[n + 1] = 0x3f3f3f3f; int ori = calc(1); int l = 1, r = m, pos = 1; while (l <= r) { int mid = (l + r) >> 1; if (calc(mid) == ori) { pos = mid; l = mid + 1; } else r = mid - 1; } long long ans = 1ll * ori * pos + 1ll * (ori + 1) * (m - pos); printf("%lld\n", ans); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
定义一个序列是好的,当且仅当它存在某种划分成左右两部分的方案,使左半部分的最大值严格小于右半部分的最小值。
现给出长度为的序列 ,保证 是一个 的排列。
次询问,每次询问 的子区间 是否为好的序列。
。
补题时胡了一个在线做法,一看题解发现全是离线扫描(
考虑从左向右扫过区间,只需检查
为方便起见,令
设
和倍增做法本质相同。在
以下为 solution 1 代码, solution 2 代码略。
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> #include <cmath> using namespace std; // #define Debug // #define LOCAL // #define TestCases const int N = 3e5, Lg = 18; int n, q; int a[N + 5]; int stk[N + 5], top; int f[N + 5]; int st[N + 5][Lg + 5]; int g[N + 5]; int queryMin(int l, int r) { int k = log2(r - l + 1); return min(st[l][k], st[r - (1 << k) + 1][k]); } int jmp[N + 5][Lg + 5], Max[N + 5][Lg + 5]; bool calc(int l, int r) { int mx = 0; for (int k = Lg; k >= 0; k--) { if (jmp[l][k] > r) continue; mx = max(mx, Max[l][k]); l = jmp[l][k]; } return mx >= r; } void solve() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", a + i); a[n + 1] = N + 1; stk[++top] = n + 1; for (int i = n; i > 0; i--) { while (a[stk[top]] < a[i]) top--; f[i] = stk[top]; stk[++top] = i; } for (int i = 1; i <= n + 1; i++) st[i][0] = a[i]; for (int k = 1; (1 << k) <= n + 1; k++) for (int i = 1; i + (1 << k) - 1 <= n + 1; i++) st[i][k] = min(st[i][k - 1], st[i + (1 << (k - 1))][k - 1]); for (int i = 1; i <= n; i++) { int l = f[i], r = n + 1; while (l <= r) { int mid = (l + r) >> 1; if (queryMin(f[i], mid) > a[i]) { g[i] = mid; l = mid + 1; } else r = mid - 1; } } f[n + 1] = g[n + 1] = n + 1; for (int i = 1; i <= n + 1; i++) { jmp[i][0] = f[i]; Max[i][0] = g[i]; } for (int k = 1; k <= Lg; k++) for (int i = 1; i <= n + 1; i++) { jmp[i][k] = jmp[ jmp[i][k - 1] ][k - 1]; Max[i][k] = max(Max[i][k - 1], Max[ jmp[i][k - 1] ][k - 1]); } scanf("%d", &q); for (int i = 1, l, r; i <= q; i++) { scanf("%d%d", &l, &r); if (calc(l, r)) puts("Yes"); else puts("No"); } return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
Alice 和你玩游戏。有一个
的网格,初始时没有颜色。Alice 在游戏开始前依次给其中 个格子分别涂上了第 种颜色,并告诉你每个颜色的位置。
接下来的每次操作,你可以选择一个未涂色的格子,由 Alice 在种颜色中选择一个涂在该格子上,并告诉你该颜色。
如果在某次操作后方格图上存在四个不同颜色的点,且它们的位置形成一个平行于边线的矩形,则输出它们以获得胜利。
你至多进行次操作,请构造一个获胜方案。交互库自适应,也就是说 Alice 的决策与你的选择有关。
。
经典 trick 的新应用(?)
很久之前教练就讲过“ 把格子看作行和列之间的边 ”的 trick ,没想到在这道题也能用!
建立一张左右各
目标转化为在二分图中找一个四元环且各边颜色不同。注意到原图共
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> #include <vector> #include <string> using namespace std; // #define Debug // #define LOCAL #define TestCases const int N = 1000, M = N << 1; int n; vector<int> e[M + 5]; int val[N + 5][N + 5]; int seq[M + 5], len; int stk[M + 5], top; int vis[M + 5], dfn; bool findcir(int u, int fa) { if (vis[u] == dfn) { while (stk[top] != u) { seq[++len] = stk[top]; top--; } seq[++len] = stk[top]; top--; reverse(seq + 1, seq + len + 1); if (seq[1] > n) { seq[len + 1] = seq[1]; for (int i = 1; i <= len; i++) seq[i] = seq[i + 1]; } return true; } stk[++top] = u; vis[u] = dfn; for (unsigned int i = 0; i < e[u].size(); i++) { int v = e[u][i]; if (v == fa) continue; if (findcir(v, u)) return true; } top--; return false; } void query(int x, int y) { printf("? %d %d\n", x, y); fflush(stdout); int res; scanf("%d", &res); val[x][y] = res; return ; } int getval(int id) { if (seq[id] > n) return seq[id] - n; return seq[id]; } void solve() { scanf("%d", &n); for (int i = 1, x, y; i <= n + n; i++) { scanf("%d%d", &x, &y); e[x].push_back(y + n); e[y + n].push_back(x); val[x][y] = i; } dfn++; for (int i = 1; i <= n; i++) { top = len = 0; if (vis[i] != dfn && findcir(i, 0)) break; } while (len > 4) { int d = len >> 1; if (len % 4 == 0) { int x = getval(1), y = getval(d); query(x, y); bool ok = true; for (int i = 1; i < d; i++) { int xx = getval(i), yy = getval(i + 1); if (i & 1) ok &= (val[xx][yy] != val[x][y]); else ok &= (val[yy][xx] != val[x][y]); } if (ok) len = d; else { for (int i = d; i <= len; i++) seq[i - d + 2] = seq[i]; len = d + 2; } } else { int x = getval(1), y = getval(d + 1); query(x, y); bool ok = true; for (int i = 1; i <= d; i++) { int xx = getval(i), yy = getval(i + 1); if (i & 1) ok &= (val[xx][yy] != val[x][y]); else ok &= (val[yy][xx] != val[x][y]); } if (ok) len = d + 1; else { for (int i = d + 1; i <= len; i++) seq[i - d + 1] = seq[i]; len = d + 1; } } } printf("! %d %d %d %d\n", getval(1), getval(3), getval(2), getval(4)); fflush(stdout); string result; cin >> result; for (int i = 1; i <= n; i++) e[i].clear(), e[i + n].clear(); for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) val[i][j] = 0; return ; } int tmp[M + 5]; int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
给定一整数序列
,所有数字在 和 之间,可以重复。按如下方式计算序列 的特征:
- 令
为最小的 ,满足 出现了 中所有数值。具体地, 。如果不存在这样的 ,令 。 的特征定义为序列 。
组数据,给出 ,请判断是否存在一种 使它的特征恰好为 ,有解需给出构造。
不错的题目,官方题解把 大概 思路说的很清楚。
Q:大概思路?
A:对,就是 大概 思路。
你品,你细品(
显然一个合法的 我也不知道为什么一定要特判,只是我的做法不判会wa,而且看江莉代码先判了这个(
考虑计算
从后往前递推
显然
这说明 中至少出现了一次 ,则 。注意这里和官方题解不同,如果 ,那么 比 优,矛盾。(detail++)
中非 的值一定是两两不同的。对于 相同的一段,随着 减小, 的下界降低而上界不变,则 应贪心地选择当前范围内没被占用的最大值。
但有一个例外,当
结束了吗?并没有。能构造出
我们可以二分最小的
本题存在线性做法,比如江莉的赛时代码,但是我不会 而且懒 (
代码中关于二分的注释有一点问题。
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; // #define Debug // #define LOCAL #define TestCases const int N = 2e5; int n; int r[N + 5]; int nxt[N + 5]; int used[N + 5], dfn; int have[N + 5]; int ans[N + 5]; bool check(int k) { dfn++; nxt[n] = n + 1; for (int i = n - 1; i > 0; i--) { if (r[i] != r[i + 1]) nxt[i] = r[i + 1]; else nxt[i] = 0; used[nxt[i]] = dfn; } for (int i = n; i > 0; i--) k -= (nxt[i] == n + 1); if (k < 0) return false; used[r[1]] = dfn;//ensure that no nxt[i] = r[1] for (int i = n - 1, pos = n; i > 0; i--) { if (nxt[i])//which means r[i] != r[i + 1] continue; if (r[i + 1] == n + 1 && k > 0) nxt[i] = n + 1, k--; else { pos = min(pos, r[i] - 1);//if nxt[i] == r[i], then i ~ (r[i] - 1) is ok while (used[pos] == dfn) pos--; if (pos <= i) return false; nxt[i] = pos; } used[nxt[i]] = dfn; } return true; } void solve() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", r + i); /*check if input is illegal*/ if (r[1] > n) return puts("No"), void(); for (int i = 1; i < n; i++) { if (r[i] > r[i + 1]) return puts("No"), void(); } /* binary search for the smallest k which last k nxt[] is n + 1, and there exist a possible nxt[] note that, when k is increasing, is more likely that exist a nxt[], and lessl likely to satisfy condition 4. */ int L = 1, R = n, k = -1; while (L <= R) { int mid = (L + R) >> 1; if (check(mid)) { k = mid; R = mid - 1; } else L = mid + 1; } if (k == -1) return puts("No"), void(); /*check condition 4*/ check(k); dfn++; for (int i = 1; i <= n; i++) { if (i > r[1] && have[i] != dfn) return puts("No"), void(); have[nxt[i]] = dfn; } /*construct*/ for (int i = 1, cnt = 0, x; i <= n; i++) { if (ans[i]) continue; cnt++; x = i; while (x <= n) { ans[x] = cnt; x = nxt[x]; } } puts("Yes"); for (int i = 1; i <= n; i++) printf("%d ", ans[i]); puts(""); for (int i = 1; i <= n; i++) ans[i] = 0; return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
有一张
个点的图,每个点有点权 ,初始时图上没有边。
给定。记与点 连通的所有点的点权和为 ,与点 连通的所有点的点权和为 ;若 ,那么可以在 间连无向边。
问能否使整张图变为连通图。
。
个人差略大的题目 /kk
需要从特殊情况入手 ,毕竟这是 cf div1 B ,不会上毒瘤 ds 的(。
事实上,
若第
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; // #define Debug // #define LOCAL #define TestCases const int N = 2e5; int n; long long c; long long a[N + 5]; long long need[N + 5]; int id[N + 5]; bool cmp(int x, int y) { return need[x] < need[y]; } void solve() { scanf("%d%lld", &n, &c); for (int i = 1; i <= n; i++) { scanf("%lld", a + i); need[i] = c * i - a[i]; id[i] = i; } sort(id + 2, id + n + 1, cmp); long long sum = a[1]; for (int i = 2; i <= n; i++) { if (need[id[i]] > sum) return puts("No"), void(); sum += a[id[i]]; } puts("Yes"); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
有
个点和 条线段,你可以选择 条线段删除,最大化未被线段覆盖的点的数量,输出最大值,
和区间相关的 dp ,感觉是经典类型(?)
C1 中
C2 考虑 dp ,先将线段按右端点排序 。想法还是 刻画当前状态 ,显然不能状压,考虑 从左向右扫过序列,设法实时维护。
考虑 逐个加点 。设
继承 ,即删除和 相关的线段时也造成了 的贡献 处新删了若干线段
规定
因为
具体地,转移为
现在要支持单点修改,区间查询最大值,开
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> #include <cmath> #include <vector> #include <map> using namespace std; // #define Debug // #define LOCAL #define TestCases const int N = 2e5, K = 10; const int Lg = 17; int n, m, k; typedef pair<int, int> pir; pir seg[N + 5]; int cov[N + 5], nxt[N + 5]; vector<pir> vec[N + 5]; int dp[N + 5][K + 5]; struct ST { int v[N + 1][Lg + 1]; void push(int pos, int val) { v[pos][0] = val; for (int i = 1; (1 << i) <= pos + 1; i++)//pos + 1: index start at pos 0 ! v[pos][i] = max(v[pos][i - 1], v[pos - (1 << (i - 1))][i - 1]); return ; } int query(int l, int r) { if (l > r) return -N; int k = log2(r - l + 1); return max(v[r][k], v[l + (1 << k) - 1][k]); } } table[K + 1]; void solve() { scanf("%d%d%d", &n, &m, &k); for (int i = 1, l, r; i <= m; i++) { scanf("%d%d", &l, &r); seg[i] = make_pair(l, r); cov[l]++, cov[r + 1]--; } for (int i = 1, lst = 0; i <= n; i++) { cov[i] += cov[i - 1]; nxt[i] = lst; if (cov[i] <= k) lst = i; } for (int i = 1, l, r, pos; i <= m; i++) { tie(l, r) = seg[i]; pos = r; while (pos >= l) { if (cov[pos] <= k) vec[pos].emplace_back(l, r); pos = nxt[pos]; } } dp[0][0] = 0; for (int i = 1; i <= n; i++) for (int j = 0; j <= k; j++) dp[i][j] = -N; for (int i = 1; i <= n; i++) { if (cov[i] > k) { for (int j = 0; j <= k; j++) table[j].push(i, -N); continue; } sort(vec[i].begin(), vec[i].end()); reverse(vec[i].begin(), vec[i].end()); for (int j = 0; j <= k; j++) { if (vec[i].empty()) { dp[i][j] = table[j].query(0, i - 1) + 1; continue; } if (vec[i][0].first < i) dp[i][j] = table[j].query(vec[i][0].first, i - 1) + 1; for (unsigned int t = 0; t < vec[i].size(); t++) { if (t + 1 != vec[i].size() && vec[i][t].first == vec[i][t + 1].first) continue; if (j < (int)t + 1)//signed +- unsigned = unsigned !!! break; int L = 0, R = vec[i][t].first - 1; if (t + 1 != vec[i].size()) L = vec[i][t + 1].first; dp[i][j] = max(dp[i][j], table[j - t - 1].query(L, R) + 1); } } for (int j = 0; j <= k; j++) table[j].push(i, dp[i][j]); } printf("%d\n", table[k].query(0, n)); for (int i = 1; i <= n + 1; i++) { cov[i] = 0; vec[i].clear(); } return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
function init(pos): stacks := an array that contains n stacks r[1], r[2], ..., r[n] return get(stacks, pos) function get(stacks, pos): if stacks[pos] is empty: return pos else: new_pos := the top element of stacks[pos] pop the top element of stacks[pos] return get(stacks, new_pos)
有
个栈,每个栈里面包含若干个 到 的数字。你想要知道 的值。需要注意的是,每次执行 函数都会将栈复原,因此 次函数调用都是独立的。
,所有栈大小之和不超过 。
cf *3000 的好题,正解简单得不像这个难度的题,但是确实不好自己 get the point
可以从栈大小均为
注意到,对于一个环,走完一圈回到起点,并且途中所有栈都弹出了一个栈顶元素,这启示可以消去一个环而不改变答案。因此只需逐个 dfs ,并实时消环。需要记忆化,因为基环树的树部分的答案都是根,多次询问叶子可以卡到 tle .
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> #include <vector> using namespace std; // #define Debug // #define LOCAL // #define TestCases const int N = 1e5; int n; vector<int> v[N + 5]; int ans[N + 5]; int stk[N + 5], top; int instk[N + 5], dfn; int calc(int u) { if (ans[u]) return ans[u]; if (instk[u] == dfn) { while (stk[top] != u) { v[stk[top]].pop_back(); instk[stk[top]] = 0; top--; } v[u].pop_back(); instk[u] = 0; top--; } instk[u] = dfn; stk[++top] = u; if (v[u].empty()) return u; return calc(v[u].back()); } void solve() { scanf("%d", &n); for (int i = 1, sz, x; i <= n; i++) { scanf("%d", &sz); while (sz--) { scanf("%d", &x); v[i].push_back(x); } } for (int i = 1; i <= n; i++) { if (ans[i]) continue; dfn++, top = 0; int res = calc(i); for (int j = 1; j <= top; j++) ans[stk[j]] = res; } for (int i = 1; i <= n; i++) printf("%d ", ans[i]); printf("\n"); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
考虑两张无向图
. 和 中每个点都有一个编号。称 和 相似当且仅当:
中每个节点编号互不相同, 中每个节点编号也互不相同。 中所有编号组成的集合 恰好等于 中所有编号组成的集合。 , 对应的节点在 中属于同一个连通块,当且仅当它们对应的节点在 中属于同一个连通块。 现在给定两棵大小为
的树 ,编号均为 . 你可以执行如下操作任意次:
- 从
中分别选择一个边集 ,满足 和 相似。其中, 表示仅由 中的边和它们相连的点组成的图,即边导出子图。 - 将
中的边集 与 中的边集 交换。 求任意次交换后有多少种不同的
,对 取模。
出题人是中国人。题目不错,官方题解很烂(?),中国人能不能不坑中国人啊(
阅读题解无果,经神仙芙卡米
考虑选出的边集
如果在 dag 上选择一个点 ,则它所有能到达的点都必须被选择。一次交换就是在 dag 上选择一个这样的导出子图,将所有点中包含的边(注意这是缩点之后的图,一个“点”可以是一群点和边)和点之间的边反向。注意到,操作之后这张图还是 dag ,并不会进一步缩点。不妨把 dag 中没有出边的点称为叶子。操作之后,处于选择和未选择的分界处的点成为新的叶子。这就好比 dag 是一张 A4 纸,所有边从一端指向另一端,对折并把一侧的边全部反向,那么所有边会指向折痕处。因此,一个边集
换个角度来思考, dag 上每条边的权值初始为 其实我也不会严谨说明,欢迎大佬指教。 综上,如果有 dag 中共有
结束了……吗?注意一种特例:一个大小为
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> #include <vector> using namespace std; // #define Debug // #define LOCAL #define TestCases const int N = 1e5, M = N << 1; const int Lg = 16, SIZE = M * (Lg + 1) * 2, E = N * 200; const long long P = 1e9 + 7; int n; int node; int value[SIZE + 5]; int Head[SIZE + 5], To[E + 5], Nxt[E + 5], edge = 1; void Add_edge(int u, int v) { edge++; To[edge] = v; Nxt[edge] = Head[u]; Head[u] = edge; return ; } struct Tree { int head[N + 5], to[M + 5], nxt[M + 5], tot = 1; void add_edge(int u, int v) { tot++; to[tot] = v; nxt[tot] = head[u]; head[u] = tot; return ; } void add(int u, int v) { add_edge(u, v); add_edge(v, u); } int fa[N + 5][Lg + 5], dep[N + 5]; int id[N + 5][Lg + 5]; void dfs(int u, int father, int depth) { fa[u][0] = father, dep[u] = depth; id[u][0] = ++node; value[node] = (u != 1);//root has no edge ! for (int k = 1; k <= Lg; k++) { fa[u][k] = fa[fa[u][k - 1]][k - 1]; id[u][k] = ++node; Add_edge(id[u][k], id[u][k - 1]); Add_edge(id[u][k], id[ fa[u][k - 1] ][k - 1]); } for (int i = head[u]; i; i = nxt[i]) { int v = to[i]; if (v == father) continue; dfs(v, u, depth + 1); } return ; } void add_path(int u, int v, int x)//x -> (u, v) { if (dep[u] < dep[v]) swap(u, v); for (int k = Lg; k >= 0; k--) { if (dep[fa[u][k]] < dep[v]) continue; Add_edge(x, id[u][k]); u = fa[u][k]; } if (u == v) return ; for (int k = Lg; k >= 0; k--) { if (fa[u][k] == fa[v][k]) continue; Add_edge(x, id[u][k]); Add_edge(x, id[v][k]); u = fa[u][k], v = fa[v][k]; } Add_edge(x, id[u][0]); Add_edge(x, id[v][0]); return ; } void clear(int len) { tot = 1; for (int i = 1; i <= len; i++) head[i] = 0; return ; } } tree[2]; int dfn[SIZE + 5], low[SIZE + 5], tim; int stk[SIZE + 5], top; int scc, belong[SIZE + 5], sz[SIZE + 5]; int out[SIZE + 5]; void tarjan(int u) { dfn[u] = low[u] = ++tim; stk[++top] = u; for (int i = Head[u]; i; i = Nxt[i]) { int v = To[i]; if (!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if (!belong[v]) low[u] = min(low[u], dfn[v]); else continue; } if (dfn[u] == low[u]) { scc++; while (stk[top] != u) { belong[stk[top]] = scc; sz[scc] += value[stk[top]]; top--; } belong[u] = scc; sz[scc] += value[u]; top--; } return ; } void solve() { scanf("%d", &n); for (int t = 0; t < 2; t++) { for (int i = 1, u, v; i < n; i++) { scanf("%d%d", &u, &v); tree[t].add(u, v); } tree[t].dfs(1, 0, 1); } for (int t = 0; t < 2; t++) { for (int u = 2; u <= n; u++) { int v = tree[t].fa[u][0], x = tree[t].id[u][0]; tree[t ^ 1].add_path(u, v, x); } } for (int i = 1; i <= node; i++) { if (!dfn[i]) tarjan(i); } for (int u = 1; u <= node; u++) { for (int i = Head[u]; i; i = Nxt[i]) { int v = To[i]; if (belong[u] != belong[v]) out[belong[u]]++; } } int ans = 1; for (int i = 1; i <= scc; i++) { if (!sz[i] || (sz[i] == 2 && !out[i])) continue; ans = ans * 2 % P; } printf("%d\n", ans); for (int i = 1; i <= node; i++) { Head[i] = 0; belong[i] = value[i] = dfn[i] = 0;//dfn !!! } for (int i = 1; i <= scc; i++) sz[i] = out[i] = 0; edge = 1, node = tim = scc = 0; tree[0].clear(n), tree[1].clear(n); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
给定两个序列
,将 中所有元素以任意顺序在任意位置插入 中,使得形成的新序列 的最长上升子序列最短,输出你的序列 。
场上 wa 了一发又想不出合理做法,遂摆烂
首先
肯定先把
如果
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> #include <vector> using namespace std; // #define Debug // #define LOCAL #define TestCases const int N = 2e5, M = N << 1; int n, m; int a[N + 5], b[N + 5]; vector<int> ans; void solve() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", a + i); for (int i = 1; i <= m; i++) scanf("%d", b + i); sort(b + 1, b + m + 1); reverse(b + 1, b + m + 1); a[0] = 1e9, a[n + 1] = 0; for (int i = 0, pos = 1; i <= n; i++) { if (i > 0) ans.push_back(a[i]); if (a[i] > a[i + 1]) { while (pos <= m && b[pos] <= a[i] && b[pos] >= a[i + 1]) ans.push_back(b[pos++]); } } for (auto v : ans) printf("%d ", v); printf("\n"); ans.clear(); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
你有
个彩色的方块,其中第 个方块的颜色是 。
你需要把所有方块放在架子上。总共有个架子,其中第 个架子容量为 ,即可以放 个方块,且有 。
假如有一个容量为的架子上按顺序摆放了颜色分别为 的方块,那么定义该架子的彩色值为两个具有相同颜色的不同方块之间的最小距离。如果架子上的方块颜色各不相同,那么其彩色值为其容量 。
形式化地,摆放了颜色为的方块的架子彩色值定义如下:
- 若所有颜色
各不相同,则彩色值为 。 - 否则,彩色值为最小的正整数
使得 。 对于每个架子
,有一个最小彩色值要求 ,你需要使得所有架子 的彩色值 。
每个测试点有个测试数据。对于每个数据,你需要判断是否有一种合法的放置方块的方案满足要求。若无解,输出 ,否则输出 行,第 行 个正整数表示第 个架子上按顺序放置的方块的颜色。
tricky problem (?)小怪兽说是贪心 + 繁琐的分讨,但其实只是简单贪心(
对于一种颜色,方块数量越少越容易满足,因为分布稀疏。显然只关心每种颜色的出现次数。可以得到一个贪心策略:对每个架子,优先放当前方块数量最多的颜色。架子可以按任意顺序枚举,对是否有解无影响。 Alex_wei 说可以调整证明,感性理解一下应该是对的。用优先队列维护,把当前填入的颜色从队列中删除,并把之前填过的且到下一个位置的距离为
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <map> using namespace std; // #define Debug // #define LOCAL #define TestCases const int N = 2e5; int n, m; int a[N + 5], cnt[N + 5]; int s[N + 5], d[N + 5]; priority_queue<pair<int, int> > q; vector<int> ans[N + 5]; void print() { for (int i = 1; i <= m; i++) { for (auto v : ans[i]) printf("%d ", v); printf("\n"); } return ; } void clr() { for (int i = 1; i <= n; i++) cnt[i] = 0; for (int i = 1; i <= m; i++) ans[i].clear(); return ; } void solve() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", a + i); cnt[a[i]]++; } for (int i = 1; i <= m; i++) scanf("%d", s + i); for (int i = 1; i <= m; i++) scanf("%d", d + i); for (int i = 1; i <= n; i++) { if (cnt[i]) q.emplace(cnt[i], i); } bool ok = true; for (int i = 1; i <= m && ok; i++) { for (int t = 0; t < s[i] && ok; t++) { if (q.empty()) { ok = false; continue; } auto tp = q.top(); q.pop(); cnt[tp.second]--; ans[i].push_back(tp.second); if (t - d[i] + 1 >= 0) { int col = ans[i][t - d[i] + 1]; if (cnt[col]) q.emplace(cnt[col], col); } } if (!ok) continue; for (int t = s[i] - d[i] + 1; t < s[i]; t++) { int col = ans[i][t]; if (cnt[col]) q.emplace(cnt[col], col); } } if (!ok) puts("-1"); else print(); clr(); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
给定一个连通的无向图,满足其任意一个点至多存在在一个简单环中。图由以下方式给出:
- 对于给定的每条边
,额外给出一个数 代表这条边包含了 个点。即若 ,那么这条边的实际意义应为边集 ,其中 是某个新建的点。 你需要给图中的每条边和每个点赋上
至 的边权,使得:
- 对于每条边,其连接的两点的权值的异或和不等于
,也不等于该边的边权; - 对于每个点,与其相连的所有边的权值的异或和不等于
,也不等于该点的点权。
试着求出所有合法的赋权方案数对取模的结果。
点击查看代码
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; // #define Debug // #define LOCAL // #define TestCases const int N = 5e5, M = 1e6, E = M << 1; const long long P = 998244353; int n, m; int sumV, sumE; int head[N + 5], to[E + 5], nxt[E + 5], w[E + 5], tot = 1; void add_edge(int u, int v, int d) { tot++; to[tot] = v; nxt[tot] = head[u]; w[tot] = d; head[u] = tot; return ; } void add(int u, int v, int w) { add_edge(u, v, w); add_edge(v, u, w); return ; } int dfn[N + 5], low[N + 5], tim; int stk[N + 5], top; int cnt, edcc[N + 5]; void tarjan(int u, int fr) { dfn[u] = low[u] = ++tim; stk[++top] = u; for (int i = head[u]; i; i = nxt[i]) { int v = to[i]; if ((i ^ 1) == fr) continue; if (!dfn[v]) { tarjan(v, i); low[u] = min(low[u], low[v]); } else low[u] = min(low[u], dfn[v]); } if (dfn[u] == low[u]) { cnt++; while (stk[top] != u) { edcc[stk[top]] = cnt; top--; } edcc[u] = cnt; top--; } return ; } long long sz[N + 5]; long long ksm(long long d, long long u) { long long res = 1; while (u) { if (u & 1) res = res * d % P; u >>= 1; d = d * d % P; } return res; } const long long TwoThird = 2ll * ksm(3, P - 2); void solve() { scanf("%d%d", &n, &m); sumV = n & 1, sumE = 0;//sumE = 0 !!! for (int i = 1, u, v, d; i <= m; i++) { scanf("%d%d%d", &u, &v, &d); add(u, v, d); sumV = sumV ^ (d & 1); sumE = sumE ^ ((d & 1) ^ 1); } if (sumV != sumE) return puts("0"), void(); long long ans = 1; tarjan(1, 0); for (int u = 1; u <= n; u++) { sz[edcc[u]]++; for (int i = head[u]; i; i = nxt[i]) { int v = to[i]; if (v > u) continue; if (edcc[u] == edcc[v]) sz[edcc[u]] += w[i]; else { ans = ans * ksm(TwoThird, w[i] + 1) % P; ans = ans * ksm(3, w[i]) % P; } } } for (int i = 1; i <= cnt; i++) { if (sz[i] == 1) { ans = ans * 3ll % P; continue; } long long multi = ksm(2, sz[i]); if (sz[i] & 1) multi = (multi + P - 2) % P; else multi = (multi + 2) % P; ans = ans * 2ll % P * multi % P; } printf("%lld\n", ans); return ; } int main() { #ifdef LOCAL freopen("data.in", "r", stdin); freopen("mycode.out", "w", stdout); #endif int T = 1; #ifdef TestCases scanf("%d", &T); #endif while (T--) solve(); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现