NOIP2025模拟5-div2(¿)
邪灾千面:
摩尼赛整出了个神秘 Div1/2,RT,被分层了。吓哭了,还是菜,采九朵莲罢。
于是这次神秘Div2有整整七位大蛇和一坨肝硬化参与,也是成功 \(rank\) 前十。
歌:
《梦回还》
持有一半的梦尚未回还
许三生缘定的千万羁绊
一条殊途 绝不回转
你眼中倒映的星河烂漫
是不曾见过的世外梦幻
万水千山 你陪我看
为你闯出的前方
贯穿世界的消亡
将弱小的自己藏匿抹杀
可所有你说的话
全部都被遗忘
轮转
你的手穿透我这整个胸膛
记忆与爱填满这一颗心脏
前生所有未能说过的话
在你心中无法撼动的他
所有为你而行的空幻梦想
都不及最后与你许下的愿望
在树下
持有一半的梦尚未回还
愿今生将你的心头填满
一条殊途 绝不回转
一同经历过的万千时光
不及你与他初见的模样
天月将白 赴往涂山
为你闯出的前方
贯穿世界的消亡
将弱小的自己藏匿抹杀
可所有你说的话
全部都被遗忘
轮转
你的手穿透我这整个胸膛
记忆与爱填满这一颗心脏
我穷尽碧落与黄泉之下
凝结的泪填不满的空荡
所有为你而行的空幻梦想
都不及最后与你许的愿望
为你闯出的前方
贯穿这整个世界的消亡
将这弱小的自己
在轮回之中藏匿抹杀
想要为你变强大
守护在你的身旁
就算你说过的话全都被遗忘
轮转
你的手穿透我这整个胸膛
记忆与爱填满这一颗心脏
前生所有未能说过的话
在你心中无法撼动的他
所有为你而行的空幻梦想
都不及最后与你许下的愿望
在树下
持有一半的梦尚未回还
许三生缘定的千万羁绊
这条殊途 我不回转
来相思树下
图:





T1 邻面合并
\(Div2~T1\) 难度严格大于 \(Div1\)。
题目描述:
给定一个 \(N \times M\) 的网格,每个格子上写有 \(0\) 或 \(1\)。现在用一些长方形覆盖其中写有 \(1\) 的格子,长方形的每条边都要与坐标轴平行。要求:
·每个写着 \(1\) 的格子都要被覆盖;
·长方形不可以重叠(重复绘制也会多增加性能开销);
·也不能覆盖到任何一个写着 \(0\) 的格子(不然绘制结果就不正确了)。
请问最少需要多少长方形?
Solve:
赛时先行:
首先假装它只能搞长为一的竖条或宽为一的横条,然后就是[COCI2020-2021#3] Selotejp,直接上刚刚学的轮廓线 \(DP\) 祭天。
然后考虑合并更多的长和宽,发现如果这一行跟上一行每个位置使用的横竖条种类不一样或者是干脆它俩的形状都不一样就可能会导致答案增加(形状不一样也可能某条竖条往下蔓延一点就对答案没贡献了¿),于是直接写个 \(check\) 函数 \(O(m)\) 乱搞,总时复 \(O(n2^mm)\) ,最终没搞出来遗憾离场(后来把大样例乱搞过了结果 \(70pts\) -> \(50pts\) 寄),哭了。
正解:
首先根据数据范围,状压是显然的,之后使用一种聪明一点的方式来记录每一层被划分成了多少个块:压缩的状态中二进制为一的位置为每一个块的开头,就像这样:

这样转移的时候就枚举当前行的划分方式,然后用当前总块数减去上一行划分方式相同的块数(因为可以合并起来喵),然后注意这一行如果块的开头一样,还要看原矩阵里两行块长度一不一样才能合并(毕竟凸出来一块不能给硬塞到一个矩形喵),最终时复 \(O(n2^{2m})\)。
Code:
#include <bits/stdc++.h>
using namespace std;
const int _ = 110;
int quan, ans = 999999999, n, a[_][9], dp[2][256], m;
bool o;
inline bool check(int x, int y){//这是判断当前状态是否合法(你不能给原矩阵不是1的位置瞎划分罢喵!)。
bool qian = 0;
for(int i = 1; i <= m; i ++){
if((y & (1 << (i - 1)))){
if(! a[x][i]){
return 0;
}
qian = 1;
}
if(a[x][i] && ! qian){
return 0;
}
if(! a[x][i]){
qian = 0;
}
}
return 1;
}
inline int fen(int x, int y, int z){//这是统计上一行和这一行合并完产生的答案。
int as = 0;
for(int i = 1; i <= m ;i ++){
if(y & (1 << (i - 1))){
as ++;
}
}
for(int i = 1; i <= m; i ++){
if((y & (1 << (i - 1))) && (z & (1 << (i - 1)))){
int kuai = i;
for(; a[x][kuai] && kuai <= m; kuai ++){
if(kuai != i && (y & (1 << (kuai - 1)))){
break;
}
}
bool ok = 0;
for(int j = i + 1; j < kuai; j ++){
if(z & (1 << (j - 1)) || ! a[x - 1][j]){
ok = 1;
break;
}
}
if(! ok && (! a[x - 1][kuai] || (z & (1 << (kuai - 1))))){
as --;
}
}
}
return as;
}
int main(){
freopen("merging.in", "r", stdin), freopen("merging.out", "w", stdout);
ios::sync_with_stdio(0), cin. tie(0), cout. tie(0);
cin >> n >> m;
quan = (1 << m) - 1;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
cin >> a[i][j];
}
}
memset(dp, 0x3f, sizeof(dp));
dp[0][0] = 0;
for(int i = 1; i <= n; i ++){
o ^= 1;//滚了个数组喵。
memset(dp[o], 0x3f, sizeof(dp[o]));
for(int j = 0; j <= quan; j ++){
if(check(i, j)){//看合不合法的,你不合法就转移了个棍母不是?
for(int k = 0; k <= quan; k ++){
if(dp[o ^ 1][k] != 0x3f3f3f3f){//依旧避免转移棍母。
dp[o][j] = min(dp[o][j], dp[o ^ 1][k] + fen(i, j, k));
}
}
}
}
}
for(int i = 0; i <= quan; i ++){
ans = min(ans, dp[o][i]);
}
cout << ans;
return 0;
}
T2 家具运输
我***的这题十几分钟秒了。
题目描述:
有 \(n\) 件家具,第 \(i\) 件家具的重量为 \(a_i\) 。现在小明要租一台货车运输这些家具,假设货车的载重量为 \(w\)。
小明运输的方法为:每一轮,小明会反复选择尽量重的家具搬上车,直到车的载重量不能再装为止。(也就是说,从空车开始,不断选择还没有运输过的家具中,搬上车后不会使车上家具总重量大于 \(w\) 的家具中最重的一件搬上车)然后他开车把所有车上的家具运到目的地并卸载,之后返回运下一趟。
现在小明想用不超过 \(k\) 趟运完所有家具,求车最小可能的载重量 \(w\) 。
Solve:
就算是我妹(五年级线性筛)如果让她学了这么久鸥霭,打了那么久摩尼赛也能想到是二分答案了罢喵~毕竟这个题面、这个数据范围、这个答案单调性都很容易很容易看出来是二分的呢喵。
Code:
#include <bits/stdc++.h>
using namespace std;
const int _ = 2010;
int l, r, ans, n, k, a[_];
bool v[_];
inline bool check(int x){
memset(v, 0, sizeof(v));
int tot = 0, us = 0;
for(int i = 1; i <= k; i ++){
tot = 0;
for(int j = 1; j <= n; j ++){
if(v[j] || a[j] + tot > x) continue ;
tot += a[j], us ++, v[j] = 1;
if(us == n) return 1;
if(tot == x) break;
if(tot > x){
v[j] = 0, us --;
break;
}
}
}
return us == n;
}
inline bool bbb(int x, int y){return x > y;}
int main(){
freopen("trans.in", "r", stdin), freopen("trans.out", "w", stdout);
ios::sync_with_stdio(0), cin. tie(0), cout. tie(0);
cin >> n >> k;
for(int i = 1; i <= n; i ++) cin >> a[i], r += a[i];
sort(a + 1, a + n + 1, bbb);
l = n / k, r ++;
while(l < r) (check((l + r) >> 1)) ? r = ((l + r) >> 1) : l = ((l + r) >> 1) + 1;
cout << r;
return 0;
}
T3 取石子
不知道我不娶。
我不会。
T4 选取字符串
我*了要不是赛时因为 \(T1\) 和 \(T3\) 我说不定就算切不了也有大部分分。
题目描述:
小明有一个长为 \(n\) 的字符串 \(S\) ,下标从 \(1\) 到 \(n\) 。用 \(S_i\) 表示第 \(1\) 到 \(i\) 个字符组成的前缀, \(S_0\) 表示空串,即长为 \(0\) 的前缀。现在要在 \(\{S_0,S_1,\dots , S_n\}\) 这些串中任选 \(k\) 个不同的串 \(S_{t_1}, S_{t_2}, \dots , S_{t_k}\) ,再依次选出两个字符串 \(p,q\) (可以为空串,可以相等),要求对于每个 \(j=1,2,\dots , k\), \(p, q\) 都既是 \(S_{t_i}\) 的前缀也是其后缀。空串是任何串的前缀和后缀。
求选取 \((\{S_{t_i}\},p,q)\) 的总共方案数。两个方案不同当且仅当某个 \(S_i\) 仅在其中一个方案中被选,或者二者中的 \(p,q\) 有至少一个不同。方案数对 \(998244353\) 取模。
Solve:
首先我们可以发现题上给的那个神秘东西就是让我们求字符串 \(s\) 的所有 \(border\) 然后 \(border\) 跳 \(border\) 算方案数,于是可以使用尊贵的烤馍片( \(KMP\) )来预处理。
之后肝硬化知道树形结构是显然的,但是我™不会树形地痞,于是因为这些串们的 \(p\) / \(q\) 最长就是它的 \(next\) ,然后就是 \(next\) 的 \(next\) ,之后以此类推子子孙孙无穷匮也………直到 \(next=0\) 。于是可以求出每个前缀作为 \(p\) 或 \(q\) 时可以被选的前缀数量(算答案的时候顺手转移了),之后用组合数算一下答案即可。
Code:
#include <bits/stdc++.h>
using namespace std;
const int _ = 1000010, mod = 998244353;
int ci[_], n, k, nxt[_], num[_];
long long ans;
char s[_];
long long jc[_], v[_];
inline long long ksm(long long a, long long b){
long long as = 1;
while(b){
if(b & 1){
as = (as * a) % mod;
}
b >>= 1;
a = (a * a) % mod;
}
return as;
}
inline int gc(int x){
if(ci[x]){
return ci[x];
}
if(! x){
return ci[x] = 1;
}
return gc(nxt[x]) + 1;
}
inline long long c(int n, int m){
if(n < m){
return 0;
}
return ((jc[n] * v[n - m]) % mod * v[m]) % mod;
}
int main(){
freopen("string.in", "r", stdin), freopen("string.out", "w", stdout);
ios::sync_with_stdio(0), cin. tie(0), cout. tie(0);
cin >> k >> s + 1;
n = strlen(s + 1);
jc[0] = v[0] = 1;
for(int i = 1; i <= n + 1; i ++){
jc[i] = (jc[i - 1] * i) % mod;
v[i] = ksm(jc[i], mod - 2);
}
num[0] = 2;
for(int i = 2, j = 0; i <= n; i ++){
while(j && s[i] != s[j + 1]){
j = nxt[j];
}
if(s[i] == s[j + 1]){
j ++;
}
nxt[i] = j;
num[j] ++;
}
for(int i = 0; i <= n; i ++){
if(! ci[i]){
ci[i] = gc(i);
}
}
for(int i = n; i >= 0; i --){
ans = (ans + c(num[i] + (i != 0), k) * ((2ll * ci[i] % mod - 1 + mod) % mod) % mod) % mod;
num[nxt[i]] = num[nxt[i]] + num[i];
}
cout << ans;
return 0;
}
END.

本文来自博客园,作者:养鸡大户肝硬化,转载请注明原文链接:https://www.cnblogs.com/rain20100708/p/19208444

喜报——肝硬化被分层辣!!!
浙公网安备 33010602011771号