A. Bestie
给出一个数组a,通过下面若干次操作使得所有数的gcd为1:选择任意一个数,将它换成gcd(a[i],i),代价是n-i+1,计算将数组变为满足条件所需的最小代价。
思路:首先我们需要知道,只要数组中有两个数互质就可以满足条件,因为要最小化代价,所以从最后向前修改一定是最优的,如果数组已经满足条件了,那代价为0;若修改最后一个可以满足条件,那就只修改最后一个,代价为1;若只修改倒数第二个就可以满足条件,那代价为2;若前面的操作都不能满足条件,那一定是要修改两个,即倒数两个,代价为3。
AC Code:
#include <bits/stdc++.h>
typedef long long ll;
const int N=1e5+5;
int t,n;
int a[N];
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin>>t;
while(t--){
std::cin>>n;
for(int i=1;i<=n;i++){
std::cin>>a[i];
}
int gcd=std::__gcd(a[1],a[2]);
for(int i=3;i<=n;i++){
gcd=std::__gcd(gcd,a[i]);
}
if(n==1){
if(a[1]==1) std::cout<<0<<'\n';
else std::cout<<1<<'\n';
continue;
}
if(gcd==1){
std::cout<<0<<'\n';
continue;
}
int x=a[n];
a[n]=std::__gcd(a[n],n);
gcd=std::__gcd(a[1],a[2]);
for(int i=3;i<=n;i++){
gcd=std::__gcd(gcd,a[i]);
}
if(gcd==1){
std::cout<<1<<'\n';
continue;
}
a[n]=x;
a[n-1]=std::__gcd(a[n-1],n-1);
gcd=std::__gcd(a[1],a[2]);
for(int i=3;i<=n;i++){
gcd=std::__gcd(gcd,a[i]);
}
if(gcd==1){
std::cout<<2<<'\n';
continue;
}
std::cout<<3<<'\n';
}
return 0;
}
B. Ugu
给出一个01串,每次可以选择一个位置,将这个位置以后的所有数全部翻转,即0变为1,1变为0,求最少的操作次数,使得01串变为非递减序列。
思路:贪心求解,每次遇到与前一个状态不同的都要翻转,要保证0在前。
AC Code:
#include <bits/stdc++.h>
typedef long long ll;
const int N=1e5+5;
int t,n;
std::string s;
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin>>t;
while(t--){
std::cin>>n;
std::cin>>s;
int cnt=0;
int flag=0;
for(int i=1;i<n;i++){
if(s[i]<s[i-1]&&!flag){
cnt++;
flag^=1;
}
if(s[i]>s[i-1]&&flag){
cnt++;
flag^=1;
}
}
std::cout<<cnt<<'\n';
}
return 0;
}
C1. Sheikh (Easy version)
给出一个数组a,只有一个询问,给出L,R,在a[L],a[R]之间找一个区间[l,r],使得区间内所有数之和-区间内所有数异或和的值最大且长度最短。
思路: 分析可知,定义的f(l,r)函数必定是随着区间长度变长而增加,因为数字中无负值,异或和每加入一个数异或,只能小于等于之前的值,所以这个函数是单调递增的,我们可以采用二分解决。考虑对于长度二分,check函数内枚举左端点,根据最长的区间的函数值判断修改左右端点。
AC Code:
#include <bits/stdc++.h>
typedef long long ll;
const int N = 2e5 + 5;
int t, pos1, pos2;
ll n, q, ans, L, R;
ll dif[N], xo[N], a[N];
bool check(int mid) {
for (int i = 1; i <= n - mid + 1; i ++) {
ll res = dif[i + mid - 1] - dif[i - 1] - (xo[i + mid - 1] ^ xo[i - 1]);
if (res == ans) {
pos1 = i, pos2 = i + mid - 1;
return true;
}
}
return false;
}
int main () {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> t;
while (t --) {
std::cin >> n >> q;
for (int i = 1; i <= n; i ++) {
std::cin >> a[i];
dif[i] = dif[i - 1] + a[i];
xo[i] = xo[i - 1] ^ a[i];
}
pos1 = 0, pos2 = 0;
while (q --) {
std::cin >> L >> R;
ans = dif[R] - dif[L - 1] - (xo[R] ^ xo[L - 1]);
int l = 1, r = n;
while (l < r) {
int mid = l + r >> 1;
if (check(mid))
r = mid;
else
l = mid + 1;
}
if (l == n) pos1 = 1, pos2 = n;
std:: cout << pos1 << ' ' << pos2 << '\n';
}
}
return 0;
}
C2. Sheikh (Hard Version)
题意同C1,但是q的范围与n相同,不再是1了。
思路:如果还是按照C1的思路,时间复杂度为O(nqlogn),肯定不行,考虑优化。首先我们对于开头结尾的0而言,去掉不影响结果,所以一定要去掉;对于中间的数,我们可以定左端点,二分找右端点,需要处理每个数后面最近的非0数,枚举这些数为左端点;最重要的优化是,因为数据范围可以用64位(大约)二进制数表示,所以最多删去64个数,仅当每个数对于答案中某一位的1有唯一的贡献,例如若答案是111111,则最多有6个数对答案有贡献,即每个数贡献答案中的一位。为什么从左侧删除呢,是因为其实而言是整个数组最多删除64个数(大约),但是这64个数也有可能是左侧的,且因为右端点我们二分查找解决了,所以可以仅考虑左端点。所以n次的枚举变成了仅有64次,时间复杂度O(64*qlogn),可以过。
AC Code:
#include <bits/stdc++.h>
typedef long long ll;
const int N = 1e5 + 5;
const int mod = 1e9 + 7;
#define INF 0x3f3f3f3f
int t, n, q, L, R;
ll a[N], sum[N], xo[N];
int next[N];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> t;
while(t --) {
std::cin >> n >> q;
for(int i = 1; i <= n; i ++) {
std::cin >> a[i];
}
for(int i = 1; i <= n; i ++) {
sum[i] = sum[i - 1] + a[i];
xo[i] = xo[i - 1] ^ a[i];
}
next[n] = INF;
for(int i = n - 1; i >= 1; i --) {
if(a[i + 1] > 0)
next[i] = i + 1;
else
next[i] = next[i + 1];
}
int st, cnt, len, l, r;
while(q --) {
std::cin >> L >> R;
ll ans = sum[R] - sum[L - 1] - (xo[R] ^ xo[L - 1]);
st = L, cnt = 64; len = R - L + 1, l = L, r = R;
while(st <= R) {
if(sum[R] - sum[st - 1] - (xo[R] ^ xo[st - 1]) < ans)
break;
int left = st, right = R, mid;
while(left <= right) {
mid = left + right >> 1;
if(sum[mid] - sum[st - 1] - (xo[mid] ^ xo[st - 1]) == ans)
right = mid - 1;
else
left = mid + 1;
}
if(len > left - st + 1)
len = left - st + 1, l = st, r = left;
if(a[st] > 0) {
if(!cnt) break;
cnt--;
}
st = next[st];
if(st == INF)
break;
}
std::cout << l << ' ' << r << '\n';
}
}
return 0;
}
D1. Balance (Easy version)
有一个一开始只有0的集合,每次有两种操作,可以向集合中加入一个数x,也可以询问集合的最小的不在集合中的x的倍数。
思路:暴力求解即可,但是对于前面出现过的数字,在下一次再开始查找时要从之前的数字开始,这样做一个简单的优化,保证不会TLE。
AC Code:
#include <bits/stdc++.h>
typedef long long ll;
const int N=1e5+5;
int q;
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin>>q;
std::set<ll>ss;
ss.insert(0);
std::map<ll,ll>mp;
while(q--){
char c;
ll x;
std::cin>>c>>x;
if(c=='+'){
ss.insert(x);
}
else{
ll t=x,tmp=mp[t];
if(tmp&&!ss.count(mp[t])){
std::cout<<mp[t]<<'\n';
continue;
}
if(tmp!=0) t=mp[t];
while(ss.count(t)){
t+=x;
}
mp[x]=t;
std::cout<<mp[x]<<'\n';
}
}
return 0;
}
os:一开始因为集合中没加0WA5了。。。xD
D2. Balance (Hard version)
题意与D1相同,只是加入一个删除操作。
思路:参考hpgg的思路!在C1的基础上再开一个set存储被删除的数,每次加入时如果这个set中有这个数,那就从原来集合中删去,在输出答案时,每次在被删除的set中二分找第一个大于x的数输出,如果找到的数大于mp[x]了,就从mp[x]开始暴力查询结果。
AC Code:
#include <bits/stdc++.h>
typedef long long ll;
const int N = 1e5 + 5;
int q;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> q;
std::set<ll> ss, del;
ss.insert(0);
std::map<ll, ll> mp;
while(q --) {
char c;
ll x;
std::cin >> c >> x;
if(c == '+') {
if(del.count(x)) del.erase(x);
ss.insert(x);
}
else if(c == '-') {
ss.erase(x);
del.insert(x);
}
else {
ll t = x;
if(!mp[t]) {
while(ss.count(t))
t += x;
mp[x] = t;
std::cout << mp[x] << '\n';
}
else {
auto it = del.lower_bound(x);
bool flag = false;
for(it; it != del.end(); it ++) {
if(*it > mp[x]) break;
if(*it % x == 0) {
std::cout << *it << '\n';
flag = true;
break;
}
}
if(!flag) {
t = mp[x];
while(ss.count(t))
t += x;
std::cout << t << '\n';
mp[x] = t;
}
}
}
}
return 0;
}
os:部分题解感觉是在胡言乱语,,若有错误请指教