A. XORwice
小组训练赛,链接无法访问,具体题目地址可以直接搜索题目标题
题目大意
求(a xor x)+ (b xor x) 的最小值
解题思路
思维题。
以二进制考虑a,b:
当a=1,b=1时,为了取最小值0,当x=1时上述运算结果为0
当a=1,b=0时,无论x取何值,上述运算结果都为1
当a=0,b=0时,为了取最小值0,当x=0时上述运算结果为0
当a=0,b=1时,无论x取何值,上述运算结果都为1
所以,当a,b分别取不同值时,上述运算的最小值如下:
1,1–>0;
0,0–>0;
1,0–>1;
0,1–>1;
显然,最小值就是 a 异或 b
#include<bits/stdc++.h>
using namespace std;
int main(){
int t;
cin >> t;
while(t--){
int a,b;
cin >> a >> b;
cout << (a ^ b) << endl;
}
return 0;
}
B. Saving the City
小组训练赛,链接无法访问,具体题目地址可以直接搜索题目标题
题目大意
给一段01序列,1为有炸弹,0为无炸弹,目标是引爆所有炸弹。一个炸弹爆炸时可以触发前后相邻的两个的炸弹。可以在序列中没有炸弹的位置上放置炸弹,以使爆炸连续。
引爆一个炸弹的钱是a,放一个炸弹的钱是b。问引爆所有炸弹的最小钱。
解题思路
首先,针对任意存在1的序列,必然至少需要一次引爆的钱。
在第一次引爆炸弹后,后续的炸弹要么被之前的爆炸触发引爆,要么作为下一次单独的爆炸起点……
比如序列:11100000111000111
针对第4个”1“,要么可以在前面放置5个炸弹,被第一次的爆炸引爆,要么作为第二次单独爆炸的起点。
由上,可以使用一个vector存储”1“的位置坐标,针对每一个”1“,判定是单独爆炸便宜,还是在前面放置 v[i]-v[i-1]-1个炸弹,从而被上一次爆炸引爆便宜。
即 min((v[i]-v[i-1]-1)*b,a)
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
typedef long long ll;
int main(){
int t;
cin >> t;
while(t--){
int a,b;
cin >> a >> b;
string s;
cin >> s;
int le = s.length();
int i = 0;
vector<int> v;
while(i < le){
if(s[i] == '1'){
v.pb(i);
}
i++;
}
if(v.size() == 0){
cout << 0 << endl;
}else{
//res += min(a,b * cnt)
ll res = a;
for(int i = 1;i < v.size();i++){
if(v[i] - v[i - 1] > 1){
res += min((v[i] - v[i - 1] - 1) * b , a);
}
}
cout << res << endl;
}
}
return 0;
}
C. Non-zero Segments
小组训练赛,链接无法访问,具体题目地址可以直接搜索题目标题
题目大意
给你一个数组a,现在你不想使得数组中的任意子序列和为0,你可以在任意位置中添加任意大小的数使得达成目的。求需要进行的最小添加次数。
解题思路
题目要求数组中的任意子序列和为0,很明显可以使用前缀和,从而i~j的子序列的和就是a[j]-a[i-1]
针对上述操作构成的前后缀和序列,我们可以发现:
- 如果其中存在”0“:我们可以加个无穷大或者负无穷,从而会使得前面的+后面的数不可能为0了,从而这个前缀里是不可能再对后面的进行影响
- 如果其中存在相同的数:那么这两个前缀和的差集自然是为0,所以这里我们进行操作之后这个前缀里也是不可能再对后面的进行影响了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
int n;
cin >> n;
ll a;
map<ll,int> m;//前缀和是否出现过
ll s = 0;
ll res = 0;
m[0] = 1;//前缀和为0时,直接进行处理
for(int i = 0;i < n;i++){
scanf("%lld",&a);
s += a;
if(m.count(s)){
//前缀和为0,直接处理;或前缀和出现过,处理。
res ++;
m.clear();//由于经过处理的子序列,不可能影响后续序列,所以要清空map
s = a;//更新当前的前缀和
m[0] = 1;//前缀和为0时,直接进行处理
m[s] = 1;//记录当前的前缀和
}else{
m[s] = 1;//记录当前的前缀和
}
}
cout << res << endl;
return 0;
}
D. Discrete Acceleration
小组训练赛,链接无法访问,具体题目地址可以直接搜索题目标题
题目大意
A、B分别从起点0,终点l出发,初始速度都为1。
有n个加油站,经过加油站则速度加一,求AB相遇时间。
解题思路
直接模拟会相当麻烦。
二分枚举时间t,考虑甲通过这个时间走过的距离和乙通过这个时间走过的距离,看是否大于给的长度,如果大于就r=mid;反之l=mid;当fabs(r - l) <= 1e-6 时,循环截止。
注意a[n+1]=m,路的开始的点是0
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 100;
double a[N];
ll n,m;
// 判定给定时间t,两车的行驶距离
bool check(double t){
// l
double dis1 = 0;
double t1 = 0;
for(ll i = 1;i <= n + 1;i++){
if( t1 + (a[i] - a[i-1]) / i >= t){
dis1 += (t - t1) * (i);
break;
}else{
t1 += (a[i] - a[i-1]) / i;
dis1 = a[i];
}
}
// r
double dis2 = 0;
double t2 = 0;
ll k = 1;
for(ll i = n;i >= 0;i--,k++){
if(t2 + (a[i+1] - a[i]) / k >= t){
dis2 += (t - t2) * (k);
break;
}else{
dis2 = m - a[i]; // 反过来,比较特殊
t2 += (a[i+1] - a[i]) / (k);
}
}
if(dis1 + dis2 >= m){
return true;
}else{
return false;
}
}
int main(void){
int t;
cin >> t;
while(t--){
cin >> n >> m;
for(ll i = 0;i <= n + 10;i++) a[i] = 0;
for(ll i = 1;i <= n;i++) cin >> a[i];
a[0] = 0;
a[n+1] = m;
double l = 0;
double r = 1e18;
while(fabs( r - l) > 1e-6){
double mid = (l + r) / 2;
if(check(mid)) r = mid; //mid
else l = mid;
}
printf("%.9f\n",l);
}
return 0;
}
思路很棒,尤其是处理加速的方法,学习了,再写五遍。