Solution -「2020.12.26」 模拟赛
0x00 前言
一些吐槽。
考得很变态诶,看每道题平均两秒的时限就知道了。。。
T1 降智了想到后缀懒得打。
T2 口胡了假优化,结果和暴力分一样??
T3 黑题还绑点??
\(50 + 80 + 0 = 130\)
沦为平民了www。
0x01 T1
一 道 好 题。
题目描述不在赘述,Link。这道题抽象概括出模型后反而更复杂 ))
首先,不难往 \(dp\) 方向去想。
我们定义 \(dp[i][j]\) 表示处理到第 \(i\) 个语句时,第 \(i\) 个语句处在第 \(j\) 个缩进时的总共方案数。
找边界,你会发现只有 for 语句能贡献新的缩进,即有多少个 for 语句就最多有多少个缩进,故我们记 for 语句的个数为为 \(m\)。
如果第 \(i-1\) 个语句是一个 for 语句,则当前语句一定在上一个语句的下一个缩进。
这很显然吧,毕竟 for 语句一定不能在整个代码的最后一个。这个时候更新就很简单了。
\(dp[i][j] = dp[i - 1][j - 1] (\forall j, 1 \leq j \leq m)\)。
那如果不是 for 语句呢?
我们想一想一个缩进的性质,如果上一个语句可以处于第 \(j\) 个缩进,则当前语句处于 \(i,0 \leq i \leq j\) 都是合法的。
因为如果上一个语句处于第 \(j\) 个缩进,则一定能在上面找到对应的使它合法的 for(当然如果 \(j = 0\) 就找不到),那么在下面再接一个语句都是没问题的,相当于我们在我们找到的那个 for 下再接了一个语句。
这样就很明朗了。
\(dp[i][j] = \sum_{k = j}^m dp[i - 1][k]\)。
当然,你会发现这里其实有个后缀和,那么每次维护上一个状态的后缀和就可以实现优化。
注意,这道题玄学卡前缀和(或许是卡 long long??),我下来调就是因为这个 \(80pt\) 的。
实现就按以上转移即可。注意取模。
#include <cstdio>
typedef long long LL;
const int MAXN = 5e3 + 5;
const LL mod = (LL)1e9 + 7;
int a[MAXN];
LL dp[MAXN][MAXN], sum[MAXN];
// 第 i 个数,当前 j 个缩进。
int main() {
int n, m = 0;
scanf ("%d", &n);
for(int i = 1; i <= n; i++) {
char st[5];
scanf ("%s", &st);
if(st[0] == 's')
a[i] = 0;
else
a[i] = 1;
m += a[i];
}
m++;
dp[1][0] = 1;
for(int i = 2; i <= n; i++) {
sum[m + 1] = 0;
for(int j = m; j >= 0; j--)
sum[j] = (sum[j + 1] % mod + dp[i - 1][j] % mod) % mod;
if(a[i] == 0) {
if(a[i - 1] == 1) {
for(int j = 1; j <= m; j++)
dp[i][j] = dp[i - 1][j - 1] % mod;
continue;
}
for(int j = 0; j <= m; j++)
dp[i][j] = sum[j] % mod;
}
else {
if(a[i - 1] == 1) {
for(int j = 1; j <= m; j++)
dp[i][j] = dp[i - 1][j - 1] % mod;
continue;
}
for(int j = 0; j <= m; j++)
dp[i][j] = sum[j] % mod;
}
}
LL ans = 0;
for(int i = 0; i <= m; i++)
ans = (ans % mod + dp[n][i] % mod) % mod;
printf("%lld\n", ans);
return 0;
}
0x02 T2
link,一种少见的矩阵变换思维题。
事实证明,暴力能拿 80 分(狂喜。
不过在此,我们不再赘述暴力,考虑正解。
首先,对于逆排序其实就是将一个位置上的数变为这个数的行数或列数在原数组中所在行或列出现的地址。
也就是原题中说的:\(p_{q_i} = i, i \in [1, n]\)
定义原数组为 \(a\),以 \(a\) 为基础我们定义三元组 \((x, y, z)\),表示当前操作以后 \(a_{(x, y)} = z, x \in [1, n], y \in [1, n]\)。
那么不难发现,对原矩阵进行操作其实也可以转化成对三元组进行操作。如下。
- R:\((x, y, z)\) ---> \((x, y + 1, z)\)
- L:\((x, y, z)\) ---> \((x, y - 1, z)\)
- D:\((x, y, z)\) ---> \((x + 1, y, z)\)
- U:\((x, y, z)\) ---> \((x - 1, y, z)\)
- I:\((x, y, z)\) ---> \((x, z, y)\)
- C:\((x, y, z)\) ---> \((z, y, x)\)
实在不理解可以自己手构一个矩阵模拟一下。
很显然每一个操作都会对每一个三元组产生影响,也就是说我们只需要维护对应三元组中每个位置的变动量即可。
在操作执行完后,枚举 \(x, y\),就会有 \(ans_{(f(x + Δx), f(y + Δy))} = f(z + Δz), x \in [1, n], y \in [1, n]\)。
其中 \(f(x) = \left\{\begin{array} \\ x \mod n + n(x < 0) \\ n(n | x) \\ x \mod n(x \geq 0, n \nmid x); \end{array}\right.\)
#include <cstdio>
void Swap(int &x, int &y) {
int t = x;
x = y;
y = t;
}
const int MAXK = 5;
const int MAXN = 1005;
const int MAXL = 1e5 + 5;
int a[MAXN][MAXN], op[MAXK], w[MAXK], n, m;
char s[MAXL];
int ans[MAXN][MAXN];
int Mod(int x) {
if(x < 0)
return x % n + n;
else {
if(x % n == 0)
return n;
else
return x % n;
}
}
int main() {
int t;
scanf ("%d", &t);
while(t--) {
scanf ("%d %d", &n, &m);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
scanf ("%d", &a[i][j]);
for(int i = 0; i <= 3; i++)
op[i] = i, w[i] = 0;
scanf ("%s", s + 1);
for(int i = 1; i <= m; i++) {
if(s[i] == 'R')
w[op[2]]++;
else if(s[i] == 'L')
w[op[2]]--;
else if(s[i] == 'D')
w[op[1]]++;
else if(s[i] == 'U')
w[op[1]]--;
else if(s[i] == 'I')
Swap(op[2], op[3]);
else if(s[i] == 'C')
Swap(op[1], op[3]);
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++) {
int temp[MAXK] = {0, i, j, a[i][j]};
ans[Mod(temp[op[1]] + w[op[1]])][Mod(temp[op[2]] + w[op[2]])] = Mod(temp[op[3]] + w[op[3]]);
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++)
printf("%d ", ans[i][j]);
printf("\n");
}
}
return 0;
}
0x03 T3
众所周知,水是黑的。
link,OJ 上的题显然是喵喵喵根据「AnOI 2020」格式改的。。
首先,我们来证明一个性质:
对于一个图,其中任意两个点间的路径我们记为 \(p\)。每个路径上最大的权值为 \(q\)。则满足 \(q\) 最小的路径 \(p_0\) 一定在原图的最小生成树(森林)上。
我们设这颗最小生成树(森林)为 \(T\),两点间路径 \(p\) 上的最大值为 \(\max(p)\)。假设 \(p\) 不是 \(T\) 上的路径,则一定存在一条路径 \(p_1\) 使得 \(\max(p_1)\) 比 \(\max(p)\) 更小。我们考虑将 \(p_1\) 加入 \(T\)。
将这个路径加入后,一定会在原树上出现一个环,而这个环上的最大值一定为 \(\max(p)\)。
你会惊讶的发现,如果删掉那条权值为 \(\max(p)\) 的边,你会得到一颗新的树,而明显这才是真正的最小生成树。
推出矛盾!原性质得证。
那么我们现在得到了这样一个性质,抽象概括出来也就是说我们的答案一定在原图的 最小生成树(森林) 上,因为树上两点路径唯一,所以答案就是两点间路径上的最大边。这显然可以 树上倍增 解决。
最后讲讲预处理,也就是如何去建图。很显然,暴力 BFS 是不可取的。于是我们考虑更高效的预处理方法。
其实还是 BFS,类双向 BFS。我们首先将所有的城市装入 BFS 的队列,然后同时开始进行扩展。对一个点我们打两种标记:\(flag\) 表示这个点是由哪个城市第一次走到的,\(ans\) 表示第一个走到这个点的城市到它的距离。那么对于当前扩展到的节点,它的标记有两种情况。
假设上一个点为 \(v\),当前点为 \(u\)。
- \(ans_u\) 里没有值,也就是说当前是第一次走到,更新 \(flag_u\) 继续扩展即可。
ans[u] = ans[v] + 1;
flag[u] = flag[v];
- ans 里有值,也就是说之前有城市扩展到了,那么就可以将当前这个城市和之前扩展的城市进行连边,权值就是上一个点和这个点的 \(ans\) 之和。
Add_Edge(flag[u], flag[v], ans[u] + ans[v]);
然后按正常 BFS 继续遍历即可,完结撒花。
(代码略显冗长,仅供参考。
#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
int read() {
int k = 1, x = 0;
char s = getchar();
while (s < '0' || s > '9') {
if (s == '-')
k = -1;
s = getchar();
}
while (s >= '0' && s <= '9') {
x = (x << 3) + (x << 1) + s - '0';
s = getchar();
}
return x * k;
}
void write(int x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
void print(int x, char s) {
write(x);
putchar(s);
}
int Max(int x, int y) { return x > y ? x : y; }
int Min(int x, int y) { return x < y ? x : y; }
void Swap(int &X, int &Y) {
int t = X;
X = Y;
Y = t;
}
const int MAXN = 2e3 + 5;
const int MAXL = 4e6 + 5;
int mp[MAXN][MAXN], h, w, p;
char s[MAXN];
struct node {
int x, y;
node() {}
node(int X, int Y) {
x = X;
y = Y;
}
};
struct edge {
int u, v, w;
edge() {}
edge(int U, int V, int W) {
u = U;
v = V;
w = W;
}
} e[MAXL];
int len = 0;
int dx[4] = { 1, -1, 0, 0 };
int dy[4] = { 0, 0, 1, -1 };
int ans[MAXN][MAXN], flag[MAXN][MAXN];
queue<node> q;
void bfs() {
while (!q.empty()) {
node now = q.front();
q.pop();
for (int i = 0; i < 4; i++) {
int cx = now.x + dx[i];
int cy = now.y + dy[i];
if (cx < 1 || cx > h)
continue;
if (cy < 1 || cy > w)
continue;
if (mp[cx][cy])
continue;
if (ans[cx][cy] == -1) {
ans[cx][cy] = ans[now.x][now.y] + 1;
flag[cx][cy] = flag[now.x][now.y];
q.push(node(cx, cy));
} else if (flag[cx][cy] != flag[now.x][now.y])
e[++len] = edge(flag[now.x][now.y], flag[cx][cy], ans[cx][cy] + ans[now.x][now.y]);
}
}
}
struct data {
int ma, fa;
data() {}
data(int Ma, int Fa) {
ma = Ma;
fa = Fa;
}
} f[MAXL][25];
int fa[MAXL];
bool cmp(edge x, edge y) { return x.w < y.w; }
void Make_Set(int n) {
for (int i = 1; i <= n; i++) fa[i] = i;
}
int Find_Set(int x) {
if (fa[x] == x)
return x;
return fa[x] = Find_Set(fa[x]);
}
struct graph {
int v, w;
graph() {}
graph(int V, int W) {
v = V;
w = W;
}
};
vector<graph> g[MAXL];
void Add_Edge(int u, int v, int w) {
g[u].push_back(graph(v, w));
g[v].push_back(graph(u, w));
}
void kruskal() {
Make_Set(p);
sort(e + 1, e + len + 1, cmp);
for (int i = 1; i <= len; i++) {
int S = Find_Set(e[i].u), E = Find_Set(e[i].v);
if (S == E)
continue;
Add_Edge(S, E, e[i].w);
fa[S] = E;
}
}
int dep[MAXL];
void dfs(int x, int fa) {
for (int i = 0; i < g[x].size(); i++) {
int v = g[x][i].v;
if (v == fa)
continue;
dep[v] = dep[x] + 1;
f[v][0].fa = x;
f[v][0].ma = g[x][i].w;
for (int j = 0; j <= 20; j++) {
f[v][j + 1].fa = f[f[v][j].fa][j].fa;
f[v][j + 1].ma = Max(f[v][j].ma, f[f[v][j].fa][j].ma);
}
dfs(v, x);
}
}
int lca(int x, int y) {
if (dep[x] < dep[y])
Swap(x, y);
int ans = 0;
for (int i = 20; i >= 0; i--)
if (dep[f[x][i].fa] >= dep[y]) {
ans = Max(ans, f[x][i].ma);
x = f[x][i].fa;
}
if (x == y)
return ans;
for (int i = 20; i >= 0; i--)
if (f[x][i].fa != f[y][i].fa) {
ans = Max(ans, f[x][i].ma);
ans = Max(ans, f[y][i].ma);
x = f[x][i].fa;
y = f[y][i].fa;
}
ans = Max(ans, f[x][0].ma);
ans = Max(ans, f[y][0].ma);
return ans;
}
int main() {
// freopen("04-04.in", "r", stdin);
// freopen("ans1.out", "w", stdout);
int Q;
h = read(), w = read(), p = read(), Q = read();
for (int i = 1; i <= h; i++) {
scanf("%s", s + 1);
for (int j = 1; j <= w; j++)
if (s[j] == '.')
mp[i][j] = 0;
else
mp[i][j] = 1;
}
memset(ans, -1, sizeof ans);
for (int i = 1; i <= p; i++) {
int x = read(), y = read();
flag[x][y] = i;
ans[x][y] = 0;
q.push(node(x, y));
}
bfs();
// printf("%d\n", len);
kruskal();
for (int i = 1; i <= p; i++)
if (fa[i] == i)
dfs(i, -1);
for (int i = 1; i <= Q; i++) {
int x = read(), y = read();
if (Find_Set(x) != Find_Set(y))
print(-1, '\n');
else
print(lca(x, y), '\n');
}
return 0;
}
Solution -「2020.12.26」 模拟赛的更多相关文章
- Solution -「多校联训」假人
\(\mathcal{Description}\) Link. 一种物品有 长度 和 权值 两种属性,现给定 \(n\) 组物品,第 \(i\) 组有 \(k_i\) 个,分别为 \((1,a ...
- 2019.7.26 NOIP 模拟赛
这次模拟赛真的,,卡常赛. The solution of T1: std是打表,,考场上sb想自己改进匈牙利然后wei了(好像匈牙利是错的. 大力剪枝搜索.代码不放了. 这是什么神仙D1T1,爆蛋T ...
- Solution -「USACO 2020.12 P」Spaceship
\(\mathcal{Description}\) Link. Bessie 在一张含 \(n\) 个结点的有向图上遍历,站在某个结点上时,她必须按下自己手中 \(m\) 个按钮中处于激活状态 ...
- Solution -「USACO 2020.12 P」Sleeping Cows
\(\mathcal{Description}\) Link. 有 \(n\) 个牛棚,大小为 \(t_{1..n}\),\(n\) 头奶牛,大小为 \(s_{1..n}\),奶牛只能住进不小 ...
- Solution -「多校联训」排水系统
\(\mathcal{Description}\) Link. 在 NOIP 2020 A 的基础上,每条边赋权值 \(a_i\),随机恰好一条边断掉,第 \(i\) 条段的概率正比于 \(a ...
- Solution -「多校联训」朝鲜时蔬
\(\mathcal{Description}\) Link. 破案了,朝鲜时蔬 = 超现实树!(指写得像那什么一样的题面. 对于整数集 \(X\),定义其 好子集 为满足 \(Y\sub ...
- Solution -「洛谷 P4372」Out of Sorts P
\(\mathcal{Description}\) OurOJ & 洛谷 P4372(几乎一致) 设计一个排序算法,设现在对 \(\{a_n\}\) 中 \([l,r]\) 内的元素排 ...
- Solution -「多校联训」小卖部
\(\mathcal{Description}\) Link. 有 \(n\) 种物品,第 \(i\) 中有 \(a_i\) 个,单价为 \(b_i\).共 \(q\) 次询问,每次查询用不超 ...
- Solution -「洛谷 P6292」区间本质不同子串个数
\(\mathcal{Description}\) Link. 给定长度为 \(n\),仅包含小写字符的字符串 \(s\),\(m\) 次询问,每次询问一个子串 \(s[l:r]\) 的本质不 ...
随机推荐
- SQL注入之information_schema
在学习SQL注入时, 经常拿出来的例子就是PHP+MySQL这一套经典组合. 其中又经常提到的>=5.0版本的MySQL的内置库: information_schema 简单看一下informa ...
- Java编程小技巧(1)——方法传回两个对象
原文地址:Java编程小技巧(1)--方法传回两个对象 | Stars-One的杂货小窝 题目是个伪命题,由Java语法我们都知道,方法要么返回一个对象,要么就不返回 当有这样的情况,我们需要返回两个 ...
- hashib加密模块、logging模块
hashib加密模块 # 加密模块 1.什么是加密 将明文的数据通过一些手段变成能密文数据 密文数据的表现形式一般都是一串没有规则的字符串 2.加密算法 加密算法有很多>>>(讲文明 ...
- 个人NuGet服务搭建,BaGet保姆及部署教程
前言 应该或许大概每个公司都会有自己的NuGet包仓库吧. 不会吧!不会吧!不会吧!不会还没有自己的仓NuGet仓库吧! 开个玩笑,虽然我觉得有没有无所谓,但是为了这篇博客它必须有所谓. 在工具的选择 ...
- react 疑问集锦
在 setState 后未 re-render function component 初始化调用接口
- 详解CVE-2022-0847 DirtyPipe漏洞
摘要:本文详细介绍了CVE-2022-0847漏洞形成根因,相应补丁修复方法,通过本文让读者对CVE-2022-0847漏洞有更清晰的了解. 本文分享自华为云社区<CVE-2022-0847 D ...
- 好客租房18-jsx阶段总结
JSX 1jsx是react的核心内容 2jsx是在js代码中写HTML结构,是react中声明式的提现 3使用jsx配合嵌入的js表达式,条件渲染,列表渲染,可以描述任意ui结构 4推荐使用cals ...
- Redis 全局通用命令整理
转载请注明出处: 1.查看所有键 keys * 该命令会存在线程阻塞问题,keys 命令也可以通过正则匹配获取存在的缓存数据 2.查看键总数 dbsize dbsize命令会返回当前数据库中键的总数. ...
- 关于『进击的Markdown』:第二弹
关于『进击的Markdown』:第二弹 建议缩放90%食用 众里寻他千百度,蓦然回首,Markdown却在灯火灿烂处 MarkdownYYDS! 各位早上好! 我果然鸽稿了 Markdown 语法 ...
- 解锁!玩转 HelloGitHub 的新姿势
本文不会涉及太多技术细节和源码,请放心食用 大家好,我是 HelloGitHub 的老荀,好久不见啊! 我在完成 HelloZooKeeper 系列之后,就很少"露面了".但是我对 ...