Educational Codeforces Round 157 (Rated for Div. 2)(思路+代码)(字典树)

A. Treasure Chest

题意:在一维坐标(即X轴)下,人物初始位于0点,箱子位于 x 点,钥匙位于 y 点,人最多能带着箱子走 k 步,求打开箱子最少的步数
思路:

  • 1.如果钥匙在箱子前,直接拿钥匙然后到箱子
  • 2.如果箱子在钥匙前,拿着箱子走一段(可能到钥匙处/到不了),放下,再取拿钥匙再回来
    实现
  • 注意:拿着箱子走一段可能到钥匙处,或者还没到,此时需要判断 k 和 (y-x) 的大小关系
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
#define x first
#define y second
int const mod1=998244353;   
int const mod2=1e9+7;
int const N=5e5+10;
int const INF=0x3f3f3f3f;
bool cmp(ll x, ll y)
{
	return x > y;
} 

void solve()
{
	int x,y,k;
	int ans = 0;
	cin >> x >> y >> k;
	if(x>y) ans = x;
	else {
		x = min(x+k,y);
		ans += x;
		ans += 2*(y-x);
	}
	cout << ans << "\n";
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int T;
	cin >> T;
	while(T--) solve();
	return 0;
}

B. Points and Minimum Distance

题意:

  • 给定 2n 个值,需要组成 n 个(x,y)的坐标,并且需要选这n个点中的一点为起点,走完每个点(不需要回到起始点),求路径最小值
  • 每两个点之间的路径为 ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ |x_1-x_2|+|y_1-y_2| x1x2+y1y2

思路:

  • 转化成2n个值之间间隔相加的问题,因为两个点之间的路径为 x 坐标差绝对值加 y 坐标差绝对值,于是转化到序列中,变成将序列中 2n 个元素分成 n 队,求最小的两队间隔之和。

实现:

  • 对序列进行排序,第一个点选 2n 与 1,第二个点选 2n-1 与 2,…
  • (为什么?):路径是 ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ |x_1-x_2|+|y_1-y_2| x1x2+y1y2 ,所以最小情况是序列从1-2n每两个元素的差值和。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
#define x first
#define y second
int const mod1=998244353;   
int const mod2=1e9+7;
int const N=5e5+10;
int const INF=0x3f3f3f3f;
bool cmp(int x, int y)
{
	return x > y;
} 
void solve()
{
	int a[300]={0};
	int n;
	cin >> n;
	for(int i=1; i<=2*n; i++) cin >> a[i];
	sort(a+1,a+1+2*n,cmp);
		
	int ans = 0;
	for(int i=1; i<n; i++) {
		ans += abs(a[i]-a[i+1]);
		ans += abs(a[2*n-i+1]-a[2*n-(i+1)+1]);
	}
	
	cout << ans << "\n";
	for(int i=1; i<=n; i++) {
		cout << a[i] << " " << a[2*n-i+1] << "\n";
	}
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int T;
	cin >> T;
	while(T--) solve();
	return 0;
}

C. Torn Lucky Ticket

题意:
有n个字符串,两两之间组合,如果能满足以下两个条件即为幸运票

  • 组合之后长度为偶数
  • 组合之后前半段每位上的和等于后半段每位上的和
  • 例子( s 1 = 13 s1=13 s1=13, s 2 = 37 s2=37 s2=37, s 1 + s 2 = 1337 s1+s2=1337 s1+s2=1337)
    于是在最先的思路上,每个字符串都要与其他字符比较,时间复杂度是 O ( n 2 ) O(n^2) O(n2) ,在 n = 2 e 5 n=2e5 n=2e5 的数量下是不可接受的。

思路:

  • 构建一个数组 a [ l ] [ r ] a[l][r] a[l][r] 表示长度为 l ,并且l长度上每位上数的和位 r 的个数
  • 我们先将每个读入的数字加入到数组中。
  • 遍历每个字符串,设合并后的长度为 i i i ,会发现在合并时,与其合并的字符长度相等时非常好求,只要对应的 r 相等就可以。
  • 对于小于和大于原本字符串长度的来说,我们可以从合并本身来看,如果长一点的字符串 s 1 s1 s1和小一点的 s 2 s2 s2合并,其等价于小一点 s 2 s2 s2的和长一点的 s 1 s1 s1合并,所以我们可以只计算目标字符串中,小于其本身长度的情况。
  • 比如 s 1 = 1234 s1=1234 s1=1234 我们只计算 1 ∣ 234 , 12 ∣ 34 , 123 ∣ 4 1|234,12|34,123|4 1∣234,12∣34,123∣4 这三种分割,分别计算另一个字符串接在前面和后面的情况(12|34,我们可以接在左边,也可以接在右边),分别计算需要的长度和需要的位上的数字之和,去 a [ l ] [ r ] a[l][r] a[l][r] 里找即可。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
#define x first
#define y second
int const mod1=998244353;   
int const mod2=1e9+7;
int const N=2e5+50;
int const INF=0x3f3f3f3f;
bool cmp(int x, int y)
{
	return x > y;
}
vector<ll> a(N);
int lens(int x)
{
	if(x == 0) return 0;
	return lens(x/10) + 1;
}
int sums(int x)
{
	if(x == 0) return 0;
	return sums(x/10) + x%10;
}
int las(int x,int p)
{
	if(p == 0) return 0;
	return las(x/10,p-1) + x%10; 
}
ll mp[15][150]={0};
void solve()
{
	int n;
	cin >> n;
	for(int i=1; i<=n; i++) {
		cin >> a[i];
		mp[lens(a[i])][sums(a[i])]++;
		//cout << "a[i]:" << a[i] <<" "<< lens(a[i]) << " " << sums(a[i]) << "\n";
	}
	
	ll ans = 0;
	for(int i=1; i<=n; i++) {
		ll len = lens(a[i]);
		ll s1,s2,nl,ns;
		for(int j=1; j<len; j++) {
			if(j*2 <= len) continue;
			s2 = las(a[i],len-j);
			s1 = sums(a[i]) - s2;
			nl = 2*j - len;
			ns = s1 - s2;
			ans += mp[nl][ns];
		}
		for(int j=1; j<len; j++) {
			if(j*2 <= len) continue;
			s2 = las(a[i],j);
			s1 = sums(a[i]) - s2;
			nl = 2*j - len;
			ns = s2 - s1;
			ans += mp[nl][ns];
		}
	}
	for(int i=1; i<=n; i++) {
		int len = lens(a[i]);
		int sum = sums(a[i]);
		ans += (mp[len][sum]);
	}
	cout << ans << "\n";
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int T;
	//cin >> T;
	T = 1;
	while(T--) solve();
	return 0;
}

D. XOR Construction

题意:给 n-1 个数 a i a_i ai ,求n个数 b i b_i bi ,条件有 a i = b i − 1 ⊕ b i a_i=b_{i-1}\oplus b_i ai=bi1bi
思路:

  • 由于异或的性质 b i ⊕ b i = 0 b_i\oplus b_i = 0 bibi=0 , b i ⊕ 0 = b i b_i\oplus 0 =b_i bi0=bi

  • 可以见得我们能根据一个已知的 b i b_i bi 求出其他的 b j ( j ≠ i ) b_j(j≠i) bj(j=i)

  • 并且 m a x ( b i ( 0 < i < n ) ) < n − 1 max(b_i(0<i<n))<n-1 max(bi(0<i<n))<n1 ,可以得到 b i b_i bi 的取值就 n 种,我们完全可以枚举一个 b i b_i bi 去找是否满足条件。

  • 在枚举 b i b_i bi 求解其他 b j ( j ≠ i ) b_j(j≠i) bj(j=i) 时,明显时间复杂度是不可接受的,我们采用 字典树(01trie) 解决这个问题

  • 此处的字典树:利用 n个连续的二进制数 能表示 0 − ( 2 n − 1 ) 0-(2^n-1) 0(2n1) 十进制数的性质,构建一个结点值位 0/1 的树,如图

  • 来自知乎-pecco

  • 字典树的具体可以看pecco大佬的文章:[https://zhuanlan.zhihu.com/p/174830050]

  • 我们需要找到一个 a a a 使得 b 1 ⊕ a b_1\oplus a b1a 最大,即使得 a 和 b在相同位上的数最好不同,我们可以根据此种逻辑找到一个序列中与 a a a 相异或最大的数,如果 a ⊕ b 1 a\oplus b_1 ab1 不大于 n-1,则代表我们成功的找到了一个合适的 b 1 b_1 b1 其余的 b j ( j ≠ i ) b_j(j≠i) bj(j=i) 可以得出。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
#define x first
#define y second
int const mod1=998244353;   
int const mod2=1e9+7;
int const N=6e5+50;
int const INF=0x3f3f3f3f;
bool cmp(int x, int y)
{
	return x > y;
}
ll n,son[N][2],idx,cnt[N];
ll a[N],b[N],ans;
void insert(int x)
{
	int p = 0;
	for(int i=25; i>=0; i--) {
		int u = (x>>i) & 1;
		if(!son[p][u]) son[p][u] = ++idx;
		p = son[p][u];
	}
	cnt[p] ++; 
}
int query(int x)
{
	int p = 0;
	int res = 0;
	for(int i=25; i>=0; i--) {
		int u = (x>>i) & 1;
		if(son[p][!u]) {
			p = son[p][!u];
			res += (1<<i);
		}
		else p = son[p][u];
	}
	return res;
}
void solve()
{
	memset(a,0,sizeof a);
	memset(b,0,sizeof b);
	memset(son,0,sizeof son);
	memset(cnt,0,sizeof cnt);
	idx = ans = n = 0;
	cin >> n;
	for(int i=1; i<n; i++) {
		cin >> a[i];
		b[i] = a[i] ^ b[i-1];
		insert(b[i]);
	}
	for(int i=0; i<=n-1; i++) {
		if(query(i) < n) {
			ans = i;
			break;
		}
	}
	
	cout << ans << " ";
	for(int i=1; i<n; i++) cout << (ans ^ b[i]) << " ";
	cout << "\n";
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int T;
	//cin >> T;
	T = 1;
	while(T--) solve();
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值