ABC224

D

题目大意

有一个九个点的无向图棋盘,上面有八个棋子,一次操作能将一个棋子沿边移到空点上,问将每个棋子移到与它编号相同的点最少几步。

解题思路

考虑使用 BFS。

string 存储状态,\(s_i\) 表示 \(i\) 号格点上棋子的编号,\(0\) 表示没有棋子。

注意:一开始不能直接修改 \(s_i\),因为 \(s\) 是空串,修改一直都是空串,需要初始化为 000000000

利用 unordered_map 判断此状态是否访问过,用 map 会 TLE。

每次找到字符串中的 \(0\),用链式向前星遍历相邻点,然后交换两者位置,没有访问过放入队列即可。

如果找到状态为 123456780,就输出答案,没有说明无解,输出 \(-1\)。

代码

#include<bits/stdc++.h>
#define endl "\n"
using namespace std; const int N = 40; struct edge
{
int to, next;
} e[N << 1]; int m, tot;
int h[20];
string s = " 000000000";
queue<pair<string, int> > q;
unordered_map<string, int> mp; void add(int u, int v)
{
tot++;
e[tot].to = v;
e[tot].next = h[u];
h[u] = tot;
} int main()
{
ios :: sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> m;
for (int i = 1; i <= m; i++)
{
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
for (int i = 1; i <= 8; i++)
{
int x;
cin >> x;
s[x] = '0' + i;
}
q.push(make_pair(s, 0));
mp[s] = 1;
while (!q.empty())
{
s = q.front().first;
int x = q.front().second;
q.pop();
if (s == " 123456780")
{
cout << x << endl;
return 0;
}
int u = s.find("0");
for (int i = h[u]; i; i = e[i].next)
{
int v = e[i].to;
swap(s[u], s[v]);
if (!mp.count(s))
{
q.push(make_pair(s, x + 1));
mp[s] = 1;
}
swap(s[u], s[v]);
}
}
cout << -1 << endl;
return 0;
}

E

题目大意

给出一个矩形,上面有一些点上有数 \(w\),每个数可以到达当前行或列比它大的数,问从每个数出发最多能走几步。

解题思路

考虑将所有点建图,每个数只能变大,说明这是一个 DAG。

因此,我们考虑使用 DAG 上 dp

但在编码时可以不用建图,减少编码难度。

发现当这个数在当前行和当前列都为最大时一步都走不了,因此可以以这些状态为初始,然后逐步更新相邻点,也就是将这个过程倒过来看,由大数往小数跳。

所以可以将所有点按 \(w\) 排序,然后从后往前遍历,消除后效性。

我们定义 \(dp[i]\) 为 \(i\) 点最多能走多少步,\(a[i]\) 为 \(i\) 行当前的最大值,\(b[i]\) 为 \(i\) 列当前的最大值,\(c[i]\) 为 \(i\) 行最大值的格点上的数(也是当前最小 \(w\)),\(d[i]\) 为 \(i\) 列最大值的格点上的数。

每遍历到一个点,就有转移方程:\(dp[i] = max(a[q[i].x], b[q[i].y]) + 1\),然后更新最大值:\(a[q[i].x] = b[q[i].y] = dp[i]\)。

特别地,如果当前行或列最大值还没有被更新,可以将其初始化为 \(-1\),使上面的式子仍然成立。

但是我们考虑到会有重复值,便不能从当前行最大值转移,因此上面算法并不完全正确。

由于我们遍历从大到小,因此如果和最小的 \(w\) 重复,即 \(w = c[i]\) 或 \(w = d[i]\),那么它一定小于次小的 \(w\),可以转移。

因此我们只要再存储次小的 \(w\) 的信息即可,将上面的数组都开两维,第二维为 \(0/1\) 表示是最小值还是次小值。

转移时如果 \(w\) 小于 \(c[i][0]\) 就将最小值变为次小,然后更新当前节点:\(dp[i] = max(dp[i], a[q[i].x][0] + 1)\),列同理,大于 \(c[i][0]\) 就从次小值转移:\(dp[i] = max(dp[i], a[q[i].x][1] + 1)\)。

时间复杂度 \(O(n)\)。

代码

#include<bits/stdc++.h>
#define endl "\n"
#define ll long long
using namespace std; const int N = 2e5 + 10; struct node
{
int x, y, w, id;
} q[N]; int h, w, n, tot;
int a[N][2], b[N][2], c[N][2], d[N][2], dp[N]; bool cmp(node x, node y)
{
return x.w < y.w;
} int main()
{
ios :: sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> h >> w >> n;
for (int i = 1; i <= n; i++)
{
cin >> q[i].x >> q[i].y >> q[i].w;
q[i].id = i;
}
sort(q + 1, q + n + 1, cmp);
memset(a, -1, sizeof a);
memset(b, -1, sizeof b);
memset(c, 0x7f, sizeof c);
memset(d, 0x7f, sizeof d);
for (int i = n; i >= 1; i--)
{
if (q[i].w < c[q[i].x][0])
{
dp[q[i].id] = a[q[i].x][0] + 1;
c[q[i].x][1] = c[q[i].x][0];
c[q[i].x][0] = q[i].w;
a[q[i].x][1] = a[q[i].x][0];
}
else
{
dp[q[i].id] = a[q[i].x][1] + 1;
}
if (q[i].w < d[q[i].y][0])
{
dp[q[i].id] = max(dp[q[i].id], b[q[i].y][0] + 1);
d[q[i].y][1] = d[q[i].y][0];
d[q[i].y][0] = q[i].w;
b[q[i].y][1] = b[q[i].y][0];
}
else
{
dp[q[i].id] = max(dp[q[i].id], b[q[i].y][1] + 1);
}
a[q[i].x][0] = max(a[q[i].x][0], dp[q[i].id]);
b[q[i].y][0] = max(b[q[i].y][0], dp[q[i].id]);
}
for (int i = 1; i <= n; i++)
{
cout << dp[i] << endl;
}
return 0;
}

F

题目大意

给出一个大整数,可以往其中任意添加加号(可以不加),变成一个加法算式,问所有这种加法算式结果之和。

解题思路

考虑拆贡献

设长度为 \(n\),现在枚举到第 \(i\) 位。

  • 考虑后面的加号影响:

    设此时第 \(i\) 位是 \(x\) 位数,那么一种情况会有 \(10^{x-1}\) 的贡献,后面最左的加号在第 \(i+x-1\) 位,后面有 \(n-i-x\) 位可以随便填,那么有 \(2^{n-i-x}\) 种情况,特别地,如果后面没有加号,有 \(1\) 种情况。

    那么求和就能得到以下式子:
    \[f[i]=2^0\cdot10^{x-i}+2^0\cdot10^{x-i-1}+2^1\cdot10^{x-i-2}+2^2\cdot10^{x-i-3}+\cdot\cdot\cdot+2^{x-i-1}\cdot10^0
    \]

    不难得到递推式:

    \[f[x] = \begin{cases}
    1 & x=n\\
    10\cdot f[i + 1] + 2 ^ {x - i - 1} & 1<x<n
    \end{cases}
    \]
  • 考虑前面的加号影响:

    只需考虑情况数即可,为 \(2^{i-1}\)。

最后只要拿当前位的数乘上两个影响即可得到当前位贡献,最后求和。

代码实现可以预处理 \(2\) 的幂次,即可做到 \(O(n)\)

代码

#include<bits/stdc++.h>
#define endl "\n"
#define ll long long
using namespace std; const int N = 2e5 + 10, P = 998244353; ll n, ans;
ll f[N], g[N];
string s; int main()
{
ios :: sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> s;
n = s.length();
s = " " + s;
g[0] = 1;
for (int i = 1; i <= n; i++)
{
g[i] = (2 * g[i - 1]) % P;
}
f[n] = 1;
for (int i = n - 1; i >= 1; i--)
{
(f[i] = f[i + 1] * 10 + g[n - 1 - i]) %= P;
}
for (int i = 1; i <= n; i++)
{
(ans += (s[i] - '0') * g[i - 1] * f[i]) %= P;
}
cout << ans << endl;
return 0;
}

G

题目大意

一个骰子,有两种操作:

  • 支付 \(A\) 元,使掷出的点数 \(+1\),但不能超过 \(N\)。
  • 支付 \(B\) 元,掷一次骰子,使点数以相等概率随机变为 \(1\) 到 \(N\) 之间的整数。

一开始点数为 \(S\),求最优策略掷出 \(T\) 的花费的期望。

解题思路

首先先讨论不掷骰子只加点数的情况,当 \(s \le T\) 时,就可以花费 \(A \cdot (T-S)\) 的代价直接到达 \(T\)。

然后考虑要掷骰子,重掷骰子后加的点数就会无效,因此一定是在掷多次后,到达一个离 \(T\) 较近的位置,再加到 \(T\)

不妨设一个阈值 \(X\),当骰子随机到 \([T-X+1,T]\) 之间时,就加点数。

于是我们就可以将策略分为两部分:

  1. 掷骰子

    掷一次骰子,随机到 \([T-X+1,T]\) 之间的概率为 \(\frac{X}{N}\)。

    根据伯努利过程的结论,次数的期望就是概率的倒数,为 \(\frac{N}{X}\) ,因此此过程花费的期望 \(B \cdot \frac{N}{X}\)。
  2. 加点数

    掷出 \([T-X+1,T]\) 中 \(i\) 的概率为 \(\frac{1}{X}\),这个点走到 \(T\) 需要 \(T-i\) 次操作,那么它到 \(T\) 花费的期望就为 \(A\cdot \frac{T-i}{X}\)。

    将其中所有点数的期望求和,化简得 \(A\cdot\frac{(X-1)}{2}\)。

根据期望的线性性质,全过程的期望即为 \(B \cdot \frac{N}{X}+A\cdot\frac{(X-1)}{2}\)。

再根据均值不等式:

\[\begin{aligned}
B \cdot \frac{N}{X}+A\cdot\frac{(X-1)}{2} &= B \cdot \frac{N}{X}+A\cdot\frac{X}{2}-\frac{A}{2} \\
&\ge \sqrt{2BNA} -\frac{A}{2} \text{(当 $X=\sqrt{\frac{2BN}{A}}$ 时取等)}
\end{aligned}
\]

因此取 \(X=\sqrt{\frac{2BN}{A}}\) 附近的三个整数代入计算取最小值即可。

值得注意的是,如果取不到函数极值那么函数单调,需要取边界值\(X=1\)或\(X=T\)时的最小值。

代码

#include<bits/stdc++.h>
#define endl "\n"
using namespace std; long long n, s, t, a, b;
long double ans; inline long double get(int x)
{
return 1.0 * b * n / x + a * (x - 1) / 2.0;
} int main()
{
ios :: sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> s >> t >> a >> b;
if (s == t)
{
cout << 0 << endl;
return 0;
}
ans = 1.0 * b * n;
if (s < t)
{
ans = min(ans, (long double)(t - s) * a);
}
ans = min(ans, min(get(1), get(t)));
long long x = sqrtl(2.0 * b * n / a);
if (x <= t)
{
ans = min(ans, get(x));
if (x >= 1)
{
ans = min(ans, get(x - 1));
}
if (x <= t)
{
ans = min(ans, get(x + 1));
}
}
cout << ans << endl;
return 0;
}

随机推荐

  1. 拯救php性能的神器webman-数据库

    有了webman的这个框架,我们得看看它是怎么使用数据库的,毕竟我们不能一直用内存变量啊. 好,很简单一句话跑起来. composer require -W illuminate/database i ...

  2. Hello Markdown(完结)

    Hello Markdown Markdown是一种轻量级的「标记语言」. 专注于文字内容: 纯文本,易读易写,可以方便地纳入版本控制: 语法简单,没有什么学习成本,能轻松在码字的同时做出美观大方的排 ...

  3. 使用Tomcat插件开发WEB应用

    在Eclipse中,可以安装Tomcat插件,实现WEB应用的开发调试工作,Tomcat插件还可以支持WEB应用的热部署. 一.安装配置Tomcat插件 可以通过拷贝安装和Links方式安装Tomca ...

  4. TreeMap源码分析——基础分析(基于JDK1.6)

    常见的数据结构有数组.链表,还有一种结构也很常见,那就是树.前面介绍的集合类有基于数组的ArrayList,有基于链表的LinkedList,还有链表和数组结合的HashMap,今天介绍基于树的Tre ...

  5. Golang之学习资源参考

    使用golang开发也有一段时间,在此总结一些自己从0入门到掌握所涉及一些资源,希望可以帮助其他人 [初级] 基础语法练习:  https://gobyexample.com/ [中级] 1.gola ...

  6. golang之常用命令

    golang常用操作与命令 1.执行golang文件 go run hello_world.go 2.编译成可执行文件(交叉编译) go build hello_world 则会生成hello_wor ...

  7. 解析HTML字符串成AST树

    1. 如何将一个字符传转换成一个AST树结构. 直接上代码: const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:" ...

  8. 《JavaScript 模式》读书笔记(8)— DOM和浏览器模式2

    四.长期运行脚本 可能会注意到有时候浏览器会提示某个脚本已经运行了很长时间,是否应该停止该脚本.实际上无论要处理多么复杂的任务,都不希望应用程序发生上述事情.而且,如果该脚本的工作十分繁重,那么浏览器 ...

  9. C#中 CancellationTokenSource的妙用

    在.NET中,CancellationTokenSource.CancellationToken和Task是处理异步操作和取消任务的重要工具.本文将通过一些简单的例子,帮助你理解它们的用法和协作方式. ...

  10. PySAGES结合CUDA SPONGE增强采样

    技术背景 在前面的一篇博客中,我们介绍过PySAGES这个增强采样软件的基本安装和使用方法.该软件类似于Plumed是一个外挂增强采样软件,但是PySAGES是基于Python语言和Jax框架来实现的 ...