ABC243

ABC224

D

题目大意

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

解题思路

考虑使用 BFS。

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

注意:一开始不能直接修改 si,因为 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 位数,那么一种情况会有 10x1 的贡献,后面最左的加号在第 i+x1 位,后面有 nix 位可以随便填,那么有 2nix 种情况,特别地,如果后面没有加号,有 1 种情况。
    那么求和就能得到以下式子:

    f[i]=2010xi+2010xi1+2110xi2+2210xi3++2xi1100

    不难得到递推式:

    f[x]={1x=n10f[i+1]+2xi11<x<n

  • 考虑前面的加号影响:
    只需考虑情况数即可,为 2i1

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

代码实现可以预处理 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 元,掷一次骰子,使点数以相等概率随机变为 1N 之间的整数。

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

解题思路

首先先讨论不掷骰子只加点数的情况,当 sT 时,就可以花费 A(TS) 的代价直接到达 T

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

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

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

  1. 掷骰子
    掷一次骰子,随机到 [TX+1,T] 之间的概率为 XN
    根据伯努利过程的结论,次数的期望就是概率的倒数,为 NX ,因此此过程花费的期望 BNX
  2. 加点数
    掷出 [TX+1,T]i 的概率为 1X,这个点走到 T 需要 Ti 次操作,那么它到 T 花费的期望就为 ATiX
    将其中所有点数的期望求和,化简得 A(X1)2

根据期望的线性性质,全过程的期望即为 BNX+A(X1)2

再根据均值不等式:

BNX+A(X1)2=BNX+AX2A22BNAA2(当 X=2BNA 时取等)

因此取 X=2BNA 附近的三个整数代入计算取最小值即可。

值得注意的是,如果取不到函数极值那么函数单调,需要取边界值X=1X=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;
}
posted @   I_LOVE_MATH  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示