22 杭州训练赛

D. Tree Problem

题意
给定 n 个节点的树,对于每个节点 x,计算:

  • 经过节点 x 的,长度至少为 1 的简单路径一共有多少条?

2 ≤ n ≤ 1 0 5 2≤n≤10^5 2n105

思路
对于每个节点,考虑经过它的简单路径的条数一共有多少。
经过这个节点,那么这条链上的两个端点要么是一个在这个节点上面,一个在这个节点下面,要么两个都在下面(从一个儿子过来,再到另一个儿子去)。
因为父节点只有一个,所以不用考虑两个端点都在上面的情况。

一个端点在上面,一个节点在下面,那么路径就是上面的节点数*下面的节点数。
上面的节点数为整棵树的节点数减去当前节点的孩子数,下面节点数为其孩子数+本身。

两个节点都在下面,考虑一个端点是其本身,那么一共有孩子数条路径。否则,从一个儿子的孩子节点过来,到另一个儿子的孩子节点去,孩子数相乘。所有儿子两两组合。

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 1q105

思路
关键在于第三种操作如何处理。
发现,每次的操作 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 1n106,1m109,1s109, 1k5

思路
走路的距离最少,也就是让骑行的距离尽量长,那么就是求 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 2n50

思路
两个人,每个人可以位于任意位置,那么一共就有 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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值