给出一个数组a,通过下面若干次操作使得所有数的gcd为1:选择任意一个数,将它换成gcd(a[i],i),代价是n-i+1,计算将数组变为满足条件所需的最小代价。
思路:首先我们需要知道,只要数组中有两个数互质就可以满足条件,因为要最小化代价,所以从最后向前修改一定是最优的,如果数组已经满足条件了,那代价为0;若修改最后一个可以满足条件,那就只修改最后一个,代价为1;若只修改倒数第二个就可以满足条件,那代价为2;若前面的操作都不能满足条件,那一定是要修改两个,即倒数两个,代价为3。
AC Code:
- #include
-
- 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;
- }
给出一个01串,每次可以选择一个位置,将这个位置以后的所有数全部翻转,即0变为1,1变为0,求最少的操作次数,使得01串变为非递减序列。
思路:贪心求解,每次遇到与前一个状态不同的都要翻转,要保证0在前。
AC Code:
- #include
-
- 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
- if(s[i]
-1]&&!flag){ - cnt++;
- flag^=1;
- }
- if(s[i]>s[i-1]&&flag){
- cnt++;
- flag^=1;
- }
- }
- std::cout<
'\n'; - }
- return 0;
- }
C1. Sheikh (Easy version)
给出一个数组a,只有一个询问,给出L,R,在a[L],a[R]之间找一个区间[l,r],使得区间内所有数之和-区间内所有数异或和的值最大且长度最短。
思路: 分析可知,定义的f(l,r)函数必定是随着区间长度变长而增加,因为数字中无负值,异或和每加入一个数异或,只能小于等于之前的值,所以这个函数是单调递增的,我们可以采用二分解决。考虑对于长度二分,check函数内枚举左端点,根据最长的区间的函数值判断修改左右端点。
AC Code:
- #include
-
- 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
-
- 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
-
- 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
ss; - ss.insert(0);
- std::map
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<
'\n'; - continue;
- }
- if(tmp!=0) t=mp[t];
- while(ss.count(t)){
- t+=x;
- }
- mp[x]=t;
- std::cout<
'\n'; - }
- }
- return 0;
- }
os:一开始因为集合中没加0WA5了。。。xD
D2. Balance (Hard version)
题意与D1相同,只是加入一个删除操作。
思路:参考hpgg的思路!在C1的基础上再开一个set存储被删除的数,每次加入时如果这个set中有这个数,那就从原来集合中删去,在输出答案时,每次在被删除的set中二分找第一个大于x的数输出,如果找到的数大于mp[x]了,就从mp[x]开始暴力查询结果。
AC Code:
- #include
-
- 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
ss, del; - ss.insert(0);
- std::map
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:部分题解感觉是在胡言乱语,,若有错误请指教