2023.10.14 做题记录
2023.10.14 做题记录
P5595 歌唱比赛
一个非常简单的贪心。
先判断什么时候是 -1
,将字符串从头开始往后遍历,Z
的右边不能有 X,Y
,如果有则直接输出 -1
。
因为是 SPJ,如果该字符串有答案的话,倒着看,字母是谁的就随便给一个大的数,如果是 \(X\),则小\(X\)的数为 \(5\),小\(Y\)的数为 \(4\),其余情况同理。
CF1428B
若一个房间左边或右边是 -
,则其必符合条件,用 \(cnt1\) 来统计。
若一个房间左右都是 <
,用 \(cnt2\) 统计。
若一个房间左右都是 >
,用 \(cnt3\) 统计。
当 >
和 <
都存在时,\(2\) 和 \(3\) 类房间都不符合条件,答案为 \(cnt1\)。
当 <
存在且 >
不存在时,\(2\) 类房间符合条件,答案为 \(cnt1 + cnt2\)。
当 >
存在且 <
不存在时,\(3\) 类房间符合条件,答案为 \(cnt1 + cnt3\)。
CF743C
将 \(\frac{2}{n}\) 分为 \(\frac{1}{n}\)+\(\frac{1}{n}\),
再根据裂项公式 $\frac{1}{n}$-$\frac{1}{n+1}$=$\frac{1}{n(n-1)}$ 导出 $\frac{1}{n}$=$\frac{1}{n+1}$+$\frac{1}{n(n-1)}$
所以 $\frac{2}{n}$=$\frac{1}{n}$+$\frac{1}{n+1}$+$\frac{1}{n(n-1)}$
然后 $\frac{1}{x}$+$\frac{1}{y}$+$\frac{1}{z}$=$\frac{1}{n}$+$\frac{1}{n+1}$+$\frac{1}{n(n-1)}$
于是 $x=n,y=n+1,z=n(n-1$)
CF476D
猜结论!
观察两组样例,可以发现共同点
- 样例一输出的是
1,2,3,5
- 样例二输出结果中最小的四个数是
2,4,6,10
不难发现,二者具有二倍关系,二者二倍关系刚好是 \(k\)
猜出结论!
cin >> n >> k;
cout << (6 * n - 1) * k << endl;
for (rint i = 1; i <= n; i++)
{
int x = 6 * i - 5;
cout << x * k << " " << (x + 1) * k << " " << (x + 2) * k << " " << (x + 4) * k << endl;
}
CF1430C
继续猜结论,一直从右往左删即可,最小值为 \(2\)
因为删到最后状态一定剩两个数,由于不可能剩两个 \(1\),所以最小值为 \(2\)。
我们可以将两个数相加除以二看作将两个数消成一个数,那要让剩下的数最小,就应该先去消大的,所以从右边开始消即可。
CF1025C
第一眼平衡树,第二眼区间 dp,第三眼大水题
先化环为链,然后求环上的最长交错字段,扫一遍就完事儿了,最后对串的长度除以二。
NOIP 2022 喵了个喵
首先观察数据范围,发现卡牌种类数 $k\in\lbrace2n−2,2n−1\rbrace$,说明 $k$ 与栈数量 $n$ 之间有种神秘的关系。
先看 $k=2n-2$ 时的情形,发现 $2n-2=2(n-1)$,即每个栈分配两个不同的数,仍能留下一个空栈,把这个空栈记为 $sp$。
我们还注意到如果多于两个牌存在于一个栈中,中间的元素会比较尴尬,不好消掉,经常会造成无解情况。
于是有一种思路便呼之欲出了
每次从牌堆处理一张牌时,如果这张牌的图案在某个栈的顶部出现,那么对该栈进行操作 $1$ 并消掉两张牌;
如果这张牌的图案在某个栈 $p_i$ 的底部出现,那么对 $sp$ 栈进行操作 $1$,再对 $p_i$ 和 $sp$ 进行操作 $2$,消掉两张牌;
否则对任意一个非 $sp$ 的栈进行操作 $1$ 即可。
由于只有 $2n-2$ 种图案,这样的方法可以保证得出结果。可以骗到前 $15$ 分
15 pts代码在后面。
再看 $k=2n-1$ 时的情形,这时上面的策略就不好用了,因为无法保证让 $sp$ 一直为空且让所有其他栈内不超过三张牌。
那么怎么解决呢?
任意时刻,设牌堆顶的牌为 $u$。保留一个栈为空,记其为 $sp$。
策略 1:
条件:存在 $sp$,且 $u$ 的同类牌在场上存在 或 非 $sp$ 栈中存在至少一个栈大小不超过 $1$
- 除了 $sp$ 外,其他的栈都至多放 $2$ 个元素。每次处理一张牌时,如果 $u$ 在某个栈 $P$ 的顶部出现过,那么把 $u$ 放入 $P$;如果 $u$ 在某个栈 $P$ 的底部出现过,那么把 $u$ 放入 $sp$,对 $P$ 和 $sp$ 进行操作 $2$;否则把 $u$ 放入某个未满的非 $sp$ 栈里。
策略 2:
条件:存在 $sp$,且 $u$ 的同类牌在场上不存在 且 非 $sp$ 栈中没有栈大小不超过 $1$
- 记当前所有在栈顶的 $n-1$ 个元素构成集合 $S$。我们不断扫描之后的图案,直到遇到第一个不在 $S$ 中的图案为止。
- 如果这个图案就是 $u$:把牌堆顶的 $u$ 和现在的 $u$ 都放入 $sp$,消掉它们。因为中间的所有元素都是栈顶,可以直接放入和它们对应的栈上。
- 如果这个图案不是 $u$:那么这个图案 $v$ 是某个栈 $P$ 的栈底。记 $P$ 的栈顶为 $w$,讨论 $w$ 在刚刚扫描过的所有图案中出现次数的奇偶性:
- 奇数次:把 $u$ 放入 $sp$,所有的 $w$ 放入 $P$。这样 $v$ 再次出现的时候,$P$ 上面的 $w$ 被清空了,此时可以直接把 $v$ 消掉,$P$ 变成新的 $sp$。
- 这个过程中,其他栈顶元素放入对应栈顶即可。
- 偶数次:把 $u$ 放入 $P$,所有的 $w$ 放入 $sp$(这里放到 $P$ 里也行)。这样 $v$ 再次出现的时候,$P$ 中自顶到底分别是 $u,w,v$ 三个元素。把 $v$ 放入 $sp$,再消掉 $P$ 和 $sp$ 的栈底,这样 $P$ 中仍然只有两个元素。
- 这个过程中,其他栈顶元素放入对应栈顶即可。
关于操作限制,不用担心,因为无论哪种策略,最后都不可能操作超过 \(m\) 次。
代码如下:
#include <bits/stdc++.h> #define rint register int #define endl '\n' using namespace std; const int N = 2e6 + 5; const int M = 3e3 + 5; int T; int n, m, k; int a[N]; deque<int> stk[M];// 维护每个栈的栈底和栈顶的元素 int id[N]; // 维护所在栈的编号,若局面未出现则为 0 int num; // 辅助栈的编号 queue<int> q;// 用于维护哪些栈可以弹入元素。初始时除了辅助栈,每个栈可以弹入 2 次 vector<pair<int, int>> ans; // 保存答案 int read() { int x = 0; bool f = true; char ch = getchar(); for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = false; for(; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0'; return f ? x : (~(x - 1)); } void insert(int s, int x) // 操作 1,向 s 中插入 x { if (!stk[s].empty() && stk[s].back() == x) { stk[s].pop_back();// 若顶部两个元素相同则弹出 } else { stk[s].push_back(x);// 否则弹入 } ans.emplace_back(s, -1);// 添加入答案 } void del(int s1, int s2) // 操作 2,消除 s1 和 s2 的栈底 { stk[s1].pop_front();// 弹出栈底 stk[s2].pop_front();// 弹出栈底 ans.emplace_back(s1, s2);// 添加入答案 } bool simple(int x) // 新出来一个数 x,尝试简单相消 { int &s = id[x]; if (!s) // 若 x 未出现过 { if (q.empty()) // 没有可弹入的栈,return false { return false; } s = q.front(); q.pop(); insert(s, x); // 否则弹入下一个可弹入的栈 } else { q.push(s);// 若 x 出现过,一定能够消除,把原先 x 所在的位置弹入 q if (x == stk[s].back()) // 在栈顶 { insert(s, x); } else // 在栈底 { insert(num, x); del(num, s); } s = 0; // 然后改为局面未出现 } return true; } signed main() { cin >> T; while (T--) { cin >> n >> m >> k; for (rint i = 1; i <= m; i++) { a[i] = read(); } ans.clear(); memset(id, 0, sizeof id); num = n; while (!q.empty()) { q.pop(); } for (int i = 1; i < n; i++) { q.push(i); q.push(i); } for (rint i = 1; i <= m; i++) { if (!simple(a[i])) // 如果尝试简单相消行不通 { int u = a[i];// 记录当前数值 int r = i + 1; int v = a[r]; while (r <= m && v != u && stk[id[v]].back() == v) { v = a[++r]; // 寻找下一个不在栈顶的数 }// 此时,r 是 i 后第一个不在栈顶的下标,v 是 a[r] if (v == u) // 如果 v 正好和 u 一样 { insert(num, u); for (rint j = i + 1; j < r; j++) { simple(a[j]); } insert(num, v);// 把他们在辅助栈消除 } else // 否则 v 是某个栈 P 的栈底 { int p = id[v]; int w = stk[p].back();// 记 P 的栈顶为 w bool is_even = true;// 查看 w 出现次数奇偶性 for (rint j = i + 1; j < r; j++) { if (a[j] == w) { is_even = !is_even; } } if (is_even) // w 出现次数为偶数 { insert(p, u);// u 放入 P for (rint j = i + 1; j < r; j++) { if (a[j] == w) { insert(num, w); } else { simple(a[j]); } } insert(num, v); del(num, p);// 消除两个栈底的 v id[v] = 0; id[u] = p; } else { insert(num, u); for (rint j = i + 1; j < r; j++) { if (a[j] == w) { insert(p, w); } else { simple(a[j]); } } insert(p, v); id[v] = id[w] = 0; id[u] = num; q.push(num); num = p; } } i = r; } } printf("%d\n", (int)ans.size()); for (rint i = 0; i < (int)ans.size(); i++) { if (ans[i].second == -1) { printf("1 %d\n", ans[i].first); } else { printf("2 %d %d\n", ans[i].first, ans[i].second); } } } return 0; }
部分分
15pts
代码:#include <bits/stdc++.h> #define rint register int #define endl '\n' using namespace std; const int N = 1e6 + 5; int T; int n, m, k; int a[N]; deque<int> st[5000]; int id[N], spt; queue<int> q; vector<pair<int, int> > ans; void insert(int s, int x) { if(!st[s].empty() && st[s].back() == x) { st[s].pop_back(); } else { st[s].push_back(x); } ans.emplace_back(s, -1); } void del(int s1, int s2) { st[s1].pop_front(); st[s2].pop_front(); ans.emplace_back(s1, s2); } void simple(int x) { int &s = id[x]; if(!s) { if(q.empty()) { puts("-1"); exit(0); } s = q.front(); q.pop(); insert(s, x); } else { q.push(s); if(x == st[s].back()) { insert(s, x); } else { insert(spt, x); del(spt, s); } s = 0; } } signed main() { cin >> T; while(T--) { cin >> n >> m >> k; for(rint i = 1; i <= m; i++) { cin >> a[i]; } ans.clear(); memset(id, 0, sizeof(id)); spt = n; while(!q.empty()) { q.pop(); } for(rint i = 1; i < n; i++) { q.push(i); q.push(i); } for(rint i = 1; i <= m; i++) { simple(a[i]); } cout << ans.size() << endl; for(rint i = 0; i < ans.size(); i++) { if(ans[i].second == -1) { printf("1 %d\n", ans[i].first); } else { printf("2 %d %d\n", ans[i].first, ans[i].second); } } } return 0; }
- 奇数次:把 $u$ 放入 $sp$,所有的 $w$ 放入 $P$。这样 $v$ 再次出现的时候,$P$ 上面的 $w$ 被清空了,此时可以直接把 $v$ 消掉,$P$ 变成新的 $sp$。