D. Tree Problem
题意
给定 n 个节点的树,对于每个节点 x,计算:
- 经过节点 x 的,长度至少为 1 的简单路径一共有多少条?
2 ≤ n ≤ 1 0 5 2≤n≤10^5 2≤n≤105
思路
对于每个节点,考虑经过它的简单路径的条数一共有多少。
经过这个节点,那么这条链上的两个端点要么是一个在这个节点上面,一个在这个节点下面,要么两个都在下面(从一个儿子过来,再到另一个儿子去)。
因为父节点只有一个,所以不用考虑两个端点都在上面的情况。
一个端点在上面,一个节点在下面,那么路径就是上面的节点数*下面的节点数。
上面的节点数为整棵树的节点数减去当前节点的孩子数,下面节点数为其孩子数+本身。
两个节点都在下面,考虑一个端点是其本身,那么一共有孩子数条路径。否则,从一个儿子的孩子节点过来,到另一个儿子的孩子节点去,孩子数相乘。所有儿子两两组合。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
vector<int> e[N];
int sum[N];
int q[N], f[N];
void dfs(int x, int fa)
{
sum[x] = 1;
for(int tx : e[x])
{
if(tx == fa) continue;
f[tx] = x;
dfs(tx, x);
sum[x] += sum[tx];
}
}
signed main(){
Ios;
cin >> n;
for(int i=1;i<n;i++){
int x, y; cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1, 0);
for(int i=1;i<=n;i++)
{
int ans = 0;
ans += (sum[1] - sum[i]) * sum[i];
ans += sum[i] - 1;
int s = 0;
for(int tx : e[i])
{
if(tx == f[i]) continue;
ans += sum[tx] * s; //兄弟节点之间相互组合,当前节点和前面的所有节点都可以配对
s += sum[tx];
}
q[i] = ans;
}
cin >> T;
while(T--)
{
int x; cin >> x;
cout << q[x] << endl;
}
return 0;
}
J. IHI’s Magic String
题意
给出 q 个操作,操作有三种形式:
1 x
: 在字符串结尾加上一个字符;2
:删掉字符串结尾的字符(如果串非空);3 x y
:把当前串中所有字符x
换成字符y
。
输出最终的串。
1 ≤ q ≤ 1 0 5 1≤q≤10^5 1≤q≤105
思路
关键在于第三种操作如何处理。
发现,每次的操作 3 只对前面的增加操作有影响,因为只需要输出最终的串,如果前面增加的一个字符知道自己最后变成了什么,直接加上最终要变成的即可。
如果直接遍历其位置后面的操作 3,最坏情况下是 n^2 的。
每次只对前面的操作有影响,不妨从后往前处理,维护出来到这个位置每个字符经过后面的操作都变成了什么,然后直接加上最终变成的值。
从后往前遍历,对于每个位置存储下来26个字符在后面的所有更改操作后都变成了什么。然后再从前往后遍历,依次处理每个加入的字符。
对于这种都是字母的题目,因为只有26种,本身隐藏了一个种类很少的性质,所以要额外留意是否需要遍历26种字符。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
const int N = 200010, mod = 1e9+7;
int T, n, m;
int f[N][30];
struct node{
int k;
char x, y;
}a[N];
signed main(){
Ios;
cin >> n;
for(int i=1;i<=n;i++)
{
cin >> a[i].k;
if(a[i].k == 1) cin >> a[i].x;
else if(a[i].k == 3) cin >> a[i].x >> a[i].y;
}
for(int i=1;i<=26;i++) f[n+1][i] = i;
for(int i=n;i>=1;i--)
{
for(int j=1;j<=26;j++) f[i][j] = f[i+1][j];
if(a[i].k == 3)
{
int x = a[i].x - 'a' + 1, y = a[i].y - 'a' + 1;
f[i][x] = f[i][y];
}
}
vector<char> v;
for(int i=1;i<=n;i++)
{
if(a[i].k == 1)
{
int x = a[i].x - 'a' + 1;
x = f[i][x];
v.push_back(char(x + 'a' - 1));
}
else if(a[i].k == 2)
{
if(!v.size()) continue;
v.pop_back();
}
}
if(!v.size()) cout << "The final string is empty";
else for(char x : v) cout << x;
return 0;
}
H. Optimal Biking Strategy
题意
有一个人在一维坐标上,想要从 0 位置走到 m 位置。
路上一共有 n 个车站,每次可以在车站借车,骑行一段距离之后要将车再次停到车站。
花费
1
1
1 元可以骑行
s
s
s 米,也就是说,骑行
x
x
x 米将会花费
⌈
x
s
⌉
\lceil\frac{x} {s}\rceil
⌈sx⌉ 元。
现在有 k 元,问达到终点最少需要走路多少米?
1 ≤ n ≤ 1 0 6 , 1 ≤ m ≤ 1 0 9 , 1 ≤ s ≤ 1 0 9 , 1 ≤ k ≤ 5 1 \le n \le 10^6, 1 \le m \le 10^9,1\le s \le 10^9,\ 1\le k \le 5 1≤n≤106,1≤m≤109,1≤s≤109, 1≤k≤5
思路
走路的距离最少,也就是让骑行的距离尽量长,那么就是求 n 个车站 k 元钱最多骑行多长距离。
定义状态 f[i, j]
表示,前 i
个车站,花费 j
元骑行距离的最大值。
状态转移:
枚举所有车站,花费的总钱数,然后枚举到当前车站花费了多少钱,把这个位置作为花这个钱的最后的位置,算出前面借车的位置,从该位置来转移。(和之前租共享单车的那个题一样)
(可能在两个车站之间花费了多元,比如两个车站相距 2s,所以枚举所有能花费的钱数来转移,不只是花费 1)
假设到当前车站花费 c 元,那么最早是在 x - c*s
位置借的车,但那个位置可能没有车站,那么就要找该位置后面的第一个车站来转移,二分找第一个位置大于等于 x - c*s
的车站。
for(int i=1;i<=n;i++)
{
for(int j=1;j<=k;j++)
{
f[i][j] = f[i-1][j];
for(int cnt=1;cnt<=j;cnt++)
{
int x = a[i] - cnt*s;
auto it = lower_bound(v.begin(), v.end(), x);
int p = *it; p = mp[p];
f[i][j] = max(f[i][j], f[p][j-cnt] + a[i] - a[p]);
}
}
}
此时,时间复杂度为 O(n*k*k*logn)
,5e8,很可能超时。
而可以把寻找 花费 j 元到每个车站 i 的那个车站
提前预处理出来,把二分拉到外面。
这样复杂度就为 O(n*k*k)
。
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
const int N = 1000010, mod = 1e9+7;
int T, n, m;
int a[N];
int f[N][6];
int pre[N][6];
signed main(){
int s;
scanf("%lld%lld%lld", &n, &m, &s);
vector<int> v;
for(int i=1;i<=n;i++)
{
scanf("%lld", &a[i]);
v.push_back(a[i]);
mp[a[i]] = i;
}
int k; scanf("%lld", &k);
for(int i=1;i<=n;i++) //预处理
{
for(int j=1;j<=k;j++)
{
int x = a[i] - j*s;
auto it = lower_bound(v.begin(), v.end(), x);
int p = *it; p = mp[p];
pre[i][j] = p;
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=k;j++)
{
f[i][j] = f[i-1][j];
for(int cnt=1;cnt<=j;cnt++)
{
int p = pre[i][cnt];
f[i][j] = max(f[i][j], f[p][j-cnt] + a[i] - a[p]);
}
}
}
int ans = 1e18;
for(int i=1;i<=k;i++) ans = min(ans, m - f[n][k]);
cout << max(0ll, ans);
return 0;
}
E. Easy Problem
题意
给定 n*n 的棋盘,每个位置为 *
或 .
,表示障碍物和空地。
a 和 b 初始在两个位置,每次操作可以选定一个方向,两人同时往该方向移动一格。
如果某个人移动到的位置为边界或者障碍物,那么此人不移动。
问,最少操作多少次可以使得两个人处于同一位置?
2 ≤ n ≤ 50 2\leq n \leq 50 2≤n≤50
思路
两个人,每个人可以位于任意位置,那么一共就有 n^4 = 6e6 种图。
如果图与图之间连边的话,最坏情况下遍历所有图就能找到答案。
所以就想着把每张图换成字符串,然后串与串之间连边跑 bfs。(类似于之前做的)
但是忽略了一个问题,空间复杂度。
这样一共有 6e6 个字符串,每个字符串长度为 2500,map映射也就是要占用 1e9 的空间。。爆空间了。
后面又想到,除了两个人所在的位置,整张图都是不变的,为什么要存在整张图呢?直接存两个人的位置不就行了?两人的位置确定了,图也就确定了。
所以其实就是记录两个人位置的bfs,最多跑遍 6e6 所有情况。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
const int N = 3010, mod = 1e9+7;
int T, n, m;
char a[51][51];
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int ax, ay, bx, by;
int f[N][N];
int get(int x, int y){
return (x-1)*n + y;
}
struct node{
int ax, ay, bx, by;
};
void bfs()
{
queue<node> que;
que.push({ax, ay, bx, by});
f[get(ax, ay)][get(bx, by)] = 1;
while(que.size())
{
int ax = que.front().ax, bx = que.front().bx;
int ay = que.front().ay, by = que.front().by;
que.pop();
for(int i=0;i<4;i++)
{
int tax = ax+dir[i][0], tay = ay+dir[i][1];
int tbx = bx+dir[i][0], tby = by+dir[i][1];
if(tax<1||tax>n||tay<1||tay>n||a[tax][tay]=='*') tax = ax, tay = ay;
if(tbx<1||tbx>n||tby<1||tby>n||a[tbx][tby]=='*') tbx = bx, tby = by;
int aa = get(tax, tay), bb = get(tbx, tby);
if(f[aa][bb]) continue;
f[aa][bb] = f[get(ax, ay)][get(bx, by)] + 1;
que.push({tax, tay, tbx, tby});
if(tax == tbx && tay == tby){
cout << f[aa][bb] - 1;
exit(0);
}
}
}
}
signed main(){
Ios;
cin >> n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
cin >> a[i][j];
if(a[i][j] == 'a') ax = i, ay = j;
if(a[i][j] == 'b') bx = i, by = j;
}
bfs();
cout << "no solution";
return 0;
}