2022 Hangzhou Normal U Summer Trials
Subarrays
给定一个长度为
n
的由正整数组成的序列,请你输出该序列中子段和能被\(k\)整除的所有符合要求的子段数量
题解:组合计数 + 前缀和 + 思维
\[sum[l,r]\ \ mod\ \ k = 0\\ (sum[r]-sum[l-1]) \ \ mod \ \ k = 0\\ sum[r] \ \ mod\ \ k \equiv sum[l-1]\ \ mod \ \ k \]根据上面的推导我们可以得到:如果某个下标下的前缀和与前面某个下标下的前缀和在模\(k\)下同余,就说明两个下标之间的子段和能被\(k\)整除
所以我们只需要先计算一遍前缀和,然后对于每个下标下的前缀和利用
map
记录一下每个下标下的前缀和对\(k\)的模数即可,最后组合计数,比如说对\(k\)取模的前缀和状态一共有\(m\)个,那么我们只需要在这\(m\)个状态中任意取两个即可,即\(C_{m}^{2}\),其余的状态以此类推
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 1e5 + 10, M = 4e5 + 10;
int n, k;
int a[N];
int pre[N];
unordered_map<int, int> mp;
void solve()
{
cin >> n >> k;
for (int i = 1; i <= n; ++i)
cin >> a[i];
for (int i = 1; i <= n; ++i)
{
pre[i] = (pre[i - 1] + a[i]) % k;
mp[pre[i] % k]++;
}
int ans = 0;
ans += mp[0];
for (auto [x, y] : mp)
{
ans += (y * (y - 1)) / 2;
}
cout << ans << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
Easy Problem
在一个\(n \times n\)地图上,包含了障碍
*
,玩家a
,玩家b
,你现在需要控制两个玩家同时上下左右移动,如果遇到障碍或者边界无法移动,请你计算出两个玩家能够遇到的最短时间,如果两个玩家无法遇到,输出no solution
\(2 \le n \le 50\)
题解:BFS
\(vis[ax][ay]][bx][by]\)代表玩家\(a\)从起点运动到\((ax,ay)\)并且玩家\(b\)从起点运动到\((bx,by)\)的最小步数
在BFS过程中,如果某个玩家遇到障碍或者边界,我们保持其坐标不变即可
最后当\((ax,ay)=(bx,by)\)时,说明两个玩家遇到了,我们直接退出广搜,输出最小步数
如果广搜结束两个玩家都没有相遇,说明无法相遇,直接输出
no solution
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 50 + 5, M = 4e5 + 10;
int n;
char g[N][N];
int dis[N][N][N][N];
bool vis[N][N][N][N];
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
struct node
{
int ax, ay, bx, by;
};
void bfs(int ax, int ay, int bx, int by)
{
queue<node> q;
q.push({ax, ay, bx, by});
dis[ax][ay][bx][by] = 0;
vis[ax][ay][bx][by] = true;
while (q.size())
{
node u = q.front();
q.pop();
if (u.ax == u.bx && u.ay == u.by)
{
cout << dis[u.ax][u.ay][u.bx][u.by] << endl;
return;
}
for (int i = 0; i < 4; ++i)
{
int nax = u.ax + dir[i][0];
int nay = u.ay + dir[i][1];
int nbx = u.bx + dir[i][0];
int nby = u.by + dir[i][1];
if (nax < 1 || nax > n || nay < 1 || nay > n || g[nax][nay] == '*')
nax = u.ax, nay = u.ay;
if (nbx < 1 || nbx > n || nby < 1 || nby > n || g[nbx][nby] == '*')
nbx = u.bx, nby = u.by;
if (vis[nax][nay][nbx][nby] == true)
continue;
vis[nax][nay][nbx][nby] = true;
dis[nax][nay][nbx][nby] = dis[u.ax][u.ay][u.bx][u.by] + 1;
q.push({nax, nay, nbx, nby});
}
}
cout << "no solution" << endl;
}
void solve()
{
cin >> n;
int ax, ay, bx, by;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
{
cin >> g[i][j];
if (g[i][j] == 'a')
ax = i, ay = j;
if (g[i][j] == 'b')
bx = i, by = j;
}
bfs(ax, ay, bx, by);
}
signed main(void)
{
Zeoy;
int T = 1;
while (T--)
{
solve();
}
return 0;
}
Optimal Biking Strategy
一个人需要从起点到达距离其
p
距离远的终点,他可以选择步行或者骑车,路上有n
个自行车停靠站,他只能在自行车停靠站上车或者下车骑车需要花钱,
1
元允许骑行最多s
米,也就是说如果两个自行车站距离为x
,那么需要花费\(\lceil \frac{x}{s} \rceil\)元现在他有
k
元,他到终点最少的步行距离\(1≤n≤10^6,1≤p≤10^9,1≤s≤10^9,1≤k≤5\)
\(a_i<a_j\)
题解:线性DP + 贪心 \(O(n*k^2 + nk*logn)\)
- 状态表示:\(O(nk)\)
\(f[i][j]\)代表从起点到第\(i\)个自行车站,花了\(j\)元后的最小步行距离
状态属性:\(MIN\)
状态计算:按照从上一个站点到第\(i\)个站点花费了多少钱对集合进行划分 \(O(k)\)
- 不花钱,即从第\(i-1\)个站点步行到第\(i\)个站点:\(f[i-1][j] + a[i] - a[i-1]\)
- 花
1
元,从距离站点\(i\)最远的可以用1
元骑行到站点\(i\)的站点\(k\)骑行到站点\(i\):\(f[k][j-1]\)- 花
2
元,从距离站点\(i\)最远的可以用2
元骑行到站点\(i\)的站点\(k\)骑行到站点\(i\):\(f[k][j-2]\)- ......
注意我们需要提前预处理\(L[i][j]\):代表距离站点\(i\)最远的可以用\(j\)元骑行到站点\(i\)的站点,因为站点序列a递增,我们直接二分即可,预处理复杂度:\(O(nk*logn)\)
状态初始:\(f[i][0] = a[i]\)
答案呈现:\(f[n][k] + p - a[n]\)
const int N = 1e6 + 10, M = 8;
int n, p, s, k;
int a[N];
int f[N][M];
int L[N][M];
void solve()
{
cin >> n >> p >> s;
for (int i = 1; i <= n; ++i)
cin >> a[i];
cin >> k;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= k; ++j)
f[i][j] = INF;
for (int i = 1; i <= n; ++i)
f[i][0] = a[i];
for (int i = 1; i <= n; ++i) //二分预处理
for (int j = 1; j <= k; ++j)
L[i][j] = lower_bound(a + 1, a + n + 1, a[i] - j * s) - a;
for (int i = 1; i <= n; ++i) //DP
for (int j = 1; j <= k; ++j)
{
f[i][j] = min(f[i][j], f[i - 1][j] + a[i] - a[i - 1]);
for (int m = 1; m <= j; ++m)
f[i][j] = min(f[i][j], f[L[i][m]][j - m]);
}
cout << f[n][k] + p - a[n] << endl;
}
IHI's Magic String
现在给定一个空串,你可以对该串操作
q
次,每次操作可能是一下的情况:
- 将一个字符添加到字符串的末尾
- 删除字符串末尾的一个字符
- 将现在字符串中所有字符
x
替换成字符y
那么问你在经过题目给定的
q
次操作后,输出最后的字符串,如果最后字符串为空,输出The final string is empty
题解:思维 + 模拟 \(O(n)\)
我们思考操作\(3\)如果字符\(x\)先被字符\(y\)替换,然后字符\(y\)被字符\(z\)替换,那么实际上就等价于字符\(x\)被字符\(z\)替换,也就是操作3是存在覆盖性的,所以我们可以考虑从后往前做
我们首先设定\(f[x]\)代表字符\(x\)最后在字符串中变成的字符,初始状态下\(f[x]=x\)
对于每一个操作3给定的\(x\)和\(y\),我们可以令\(f[x] = f[y]\)
对于每一个操作1来说,我们模拟后发现,倒着做的时候我们需要在字符串前面添加字符\(f[x]\),但是问题是字符\(f[x]\)应不应该被添加,因为该字符有可能被操作2删除了,所以我们可以先按题意模拟,预处理出在每个操作1添加的字符是否被操作2删除
时间复杂度:\(O(n)\)
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 1e5 + 10, M = 4e5 + 10;
struct node
{
int op;
char x;
char y;
};
int n;
node qry[N];
stack<int> stk;
bool st[N];
void solve()
{
cin >> n;
string s;
for (int i = 1; i <= n; ++i)
{
int op;
char x, y;
cin >> op;
if (op == 1)
{
cin >> x;
qry[i].op = op;
qry[i].x = x;
s += x;
stk.push(i);
}
else if (op == 2)
{
qry[i].op = op;
if (s.size())
{
st[stk.top()] = true;
stk.pop();
s.pop_back();
}
}
else
{
cin >> x >> y;
qry[i].op = op;
qry[i].x = x;
qry[i].y = y;
}
}
if (s.empty())
{
cout << "The final string is empty" << endl;
return;
}
s = "";
char f[26];
for (int i = 0; i < 26; ++i)
f[i] = char(i + 'a');
int cnt = 0;
for (int i = n; i >= 1; i--)
{
int op = qry[i].op;
if (op == 3)
{
char x = qry[i].x, y = qry[i].y;
f[x - 'a'] = f[y - 'a'];
}
else if (op == 1)
{
if (!st[i])
{
char x = qry[i].x;
s = f[x - 'a'] + s;
}
}
}
cout << s << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}