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. js-xlsx 前段读取excel

    JavaScript读取和导出excel示例(基于js-xlsx) 放入参考链接 http://demo.haoji.me/2017/02/08-js-xlsx/ github官网 https://g ...

  2. 干货分享:开启PWM调光之门,一起来做呼吸灯

    PWM作为一种灵活且高效的信号调制手段,在电气设备的性能控制和调节中发挥着重要作用,常用于电机控制.灯光调光.音频信号生成.加热控制等应用. 本文将以合宙低功耗4G模组经典型号--Air780E为例, ...

  3. 【Flink 日常踩坑】Could not find ExecutorFactory in classpath

    Description 一段简单的 FlinkSQL 程序,在 IDE 中运行没问题,但是 maven 打包后发布到终端启动却报错了. import org.apache.flink.configur ...

  4. MySQL 主从复制之多线程复制

    目录 一.MySQL 多线程复制的背景 二.MySQL 5.5 主从复制 1.原理 2.部署主从复制 2.1.主节点安装配置MySQL 5.5 2.2.从节点安装配置MySQL 5.5 3.检查主从库 ...

  5. TypeScript名词解释系列:tsconfg中的target,module和moduleResolution

    tsconfg中的target,module和moduleResolution target 就是TypeScript文件编译后生成的javascript文件里的语法应该遵循哪个JavaScript的 ...

  6. ibatis源码分析

    背景:调试模式下,单步运行一个查询订单协议操作,记录了ibatis框架的执行动作,侧面剖析其原理. 一.简介: 1. dal 层的dao接口实现类通常会继承SqlMapClientDaoSupport ...

  7. VScode之远程开发

    之前使用过PyCharm的远程开发,很好用,不过还是有几个局限性: 只能用于Python语言: 本地和服务器都需要有一份代码,这两份代码是完全同步的: 一.配置免密远程登录 1.首先检查本地是否有已生 ...

  8. 使用gulp 压缩js

    js 编写后文件太大,可以使用gulp 来进行压缩. 具体步骤如下: 1.创建一个工作目录 在该目录下安装 gulp npm install gulp 安装gulp-uglify 模块 npm ins ...

  9. 搭建一个文件存储服务器minio,实现文件存储

    搭建一个文件存储服务器minio,实现文件存储 Minio是一个开源的.自托管的对象存储服务器,它提供了类似于云存储服务的功能.你可以使用Minio搭建自己的私有云存储解决方案,或者作为公共存储服务的 ...

  10. JavaScript是按顺序执行的吗?聊聊JavaScript中的变量提升

    作为一位前端开发者,我们经常会听到这么一句话:"JavaScript的执行是按照顺序自上而下依次执行的."这句话说的并没有错.但是它似乎又好像不完全对.我们先来看以下这段代码.你觉 ...