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| ∣x1−x2∣+∣y1−y2∣
思路:
- 转化成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| ∣x1−x2∣+∣y1−y2∣ ,所以最小情况是序列从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=bi−1⊕bi
思路:
-
由于异或的性质 b i ⊕ b i = 0 b_i\oplus b_i = 0 bi⊕bi=0 , b i ⊕ 0 = b i b_i\oplus 0 =b_i bi⊕0=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))<n−1 ,可以得到 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−(2n−1) 十进制数的性质,构建一个结点值位 0/1 的树,如图
-
-
字典树的具体可以看pecco大佬的文章:[https://zhuanlan.zhihu.com/p/174830050]
-
我们需要找到一个 a a a 使得 b 1 ⊕ a b_1\oplus a b1⊕a 最大,即使得 a 和 b在相同位上的数最好不同,我们可以根据此种逻辑找到一个序列中与 a a a 相异或最大的数,如果 a ⊕ b 1 a\oplus b_1 a⊕b1 不大于 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;
}