Codeforces Round 984 (Div. 3) 题解

Codeforces Round 984 (Div. 3)

E. Reverse the Rivers

二分优化,二维数组


E.河流倒流

每次测试时限:2 秒

每次测试的内存限制:256 兆字节

输入:标准输入

输出:标准输出

古代圣贤为了自己的方便,决定改变河流的流向,他们的阴谋将世界置于危险的边缘。但在实施他们的宏伟计划之前,他们决定仔细考虑一下他们的战略--这就是圣人的做法。

\(n\) 个国家,每个国家正好有 \(k\) 个地区。对于 \(i\) 这个国家的 \(j\) 个地区,他们计算出了 \(a_{i,j}\) 这个值,它反映了这个地区的水量。

圣人打算在 \(i\) 国家的 \(j\) /th区域和 \((i + 1)\) 国家的 \(j\) /th区域之间为所有的 \(1 \leq i \leq (n - 1)\) 和所有的 \(1 \leq j \leq k\) 修建水渠。

由于所有 \(n\) 个国家都在一个大斜坡上,所以水会流向数字最高的国家。根据圣人的预测,在通道系统建立之后, \(i\) /th国家的 \(j\) /th区域的新值将是 \(b_{i,j} = a_{1,j} | a_{2,j} | ... | a_{i,j}\) ,其中 \(|\) 表示比特 "OR"操作。

在重新分配水源之后,圣人的目的是选择最适合居住的国家,因此他们会向你提出 \(q\) 个问题供你考虑。

每个问题都包含 \(m\) 个要求。

每个要求包含三个参数:地区编号 \(r\) 、符号 \(o\) (" \(\lt;\) "或" \(\gt;\) ")和值 \(c\) 。如果 \(o\) =" \(\lt;\) ",那么在您所选国家的 \(r\) /th区域内,新值必须严格小于限值 \(c\) ;如果 \(o\) =" \(\gt;\) ",那么新值必须严格大于限值。

换句话说,所选国家 \(i\) 必须满足所有 \(m\) 要求。如果当前要求 \(o\) = " \(\lt;\) ",那么必须成立 \(b_{i,r} \lt; c\) ,如果 \(o\) = " \(\gt;\) ",那么 \(b_{i,r} \gt; c\)

在回答每个查询时,您应该输出一个整数--合适国家的编号。如果有多个这样的国家,则输出最小的一个。如果不存在这样的国家,则输出 \(-1\)

输入

第一行包含三个整数 \(n\)\(k\)\(q\) ( \(1 \leq n, k, q \leq 10^5\) ),分别是国家、地区和查询的数量。

接下来是 \(n\) 行,其中第 \(i\) 行包含 \(k\) 个整数 \(a_{i,1}, a_{i,2}, \dots, a_{i,k}\)\(1 \leq a_{i,j} \leq 10^9\) ),其中 \(a_{i,j}\) 是第 \(i\) 个国家的第 \(j\) 个地区的值。

然后,描述了 \(q\) 个查询。

每个查询的第一行都包含一个整数 \(m\)\(1 \leq m \leq 10^5\) )。( \(1 \leq m \leq 10^5\) )--需求的数量。

然后是 \(m\) 行,每行包含一个整数 \(r\) 、一个字符 \(o\) 和一个整数 \(c\) 。( \(1 \leq r \leq k\)\(0 \leq c \leq 2 \cdot 10^9\) ),其中 \(r\)\(c\) 是区域编号和数值, \(o\) 是" \(\lt;\) "或" \(\gt;\) "--符号。

可以保证 \(n \cdot k\) 不超过 \(10^5\) ,所有查询中 \(m\) 的总和也不超过 \(10^5\)


  1. n*k<=10^5 可以考虑使用vector存二维数组

  2. a | b >= max(a,b) 按位或只会增大,不会减小,很容易证明

  3. 直接暴力 复杂度为 q*n*k 到达亿级别 不予考虑

  4. 如何优化?单调递增!直接考虑二分!


#include <bits/stdc++.h>
using namespace std;
#define ll long long
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	
	ll n,k,q;
	cin>>n>>k>>q;
	vector<vector<ll>> a(k,vector<ll> (n,0));
	for(int i=0;i<n;i++){
		for(int j=0;j<k;j++){
			cin>>a[j][i];
		}
	}
	for(int i=0;i<k;i++){
		for(int j=0;j<n-1;j++){
			a[i][j+1]=a[i][j]|a[i][j+1];
		}
	}
	while(q--){
		ll m;
		cin>>m;
		ll l=0,r=n-1;
		bool err=0;
		while(m--){
			ll index,c;
			char op;
			cin>>index;
			cin>>op;
			cin>>c;
			index--;
			if(op=='>'){
				ll mid = upper_bound(a[index].begin(),a[index].end(),c)- a[index].begin();
					l = max(mid,l);
			}
			else{
				ll mid = lower_bound(a[index].begin(),a[index].end(),c) - a[index].begin();
					mid--;
					r = min(r,mid);
			}
			if(l>r){
				err=1;
			}
		}
		if(!err){
			cout<<l+1<<endl;
		}
		else{
			cout<<-1<<endl;
		}
	}
	
}

此题难度不高,但时间不足,不过很容易想到暴力作法,提交发现TLE,便考虑二分优化,缩小查找范围,从而AC

Codeforces Round 984 (Div. 3)

F.XORificator 3000

数论,找规律


F.XORificator 3000

每次测试的时间限制:1 秒

每次测试内存限制:256 兆字节

输入:标准输入

输出:标准输出

爱丽丝多年来一直给鲍勃送礼物,她知道鲍勃最喜欢的是对有趣的整数进行 bitwise XOR。鲍勃认为,正整数 \(x\) 如果满足 \(x \not\equiv k (\bmod 2^i)\) ,就是有趣的。因此,今年他生日时,她送给他一台超级强大的最新型号 "XORificator 3000"。

鲍勃对这份礼物非常满意,因为它可以让他立即计算出从 \(l\)\(r\) (包括 \(l\)\(r\) )任意范围内所有有趣整数的 XOR。毕竟,一个人的幸福还需要什么呢?不幸的是,这个装置太强大了,以至于有一次它与自己进行了 XOR 之后就消失了。鲍勃非常沮丧,为了让他开心起来,爱丽丝让你写出你版本的 "XORificator"。

输入

第一行输入包含一个整数 \(t\) \((1 \leq t \leq 10^4)\) --段上的 XOR 查询次数。 \((1 \leq t \leq 10^4)\) --段上的 XOR 查询次数。接下来的 \(t\) 行包含查询,每个查询由整数 \(l\)\(r\)\(i\)\(k\) 组成。 \((1 \leq l \leq r \leq 10^{18}\) , \(0 \leq i \leq 30\) , \(0 \leq k \lt 2^i)\) .

输出

对于每个查询,输出一个整数 - 范围为 \([l, r]\) 的所有整数 \(x\) 的 XOR,即 \(x \not\equiv k \mod 2^i\) .

在第一个查询中, \([1, 3]\) 范围内有趣的整数是 \(1\)\(3\) ,因此答案将是 \(1 \oplus 3 = 2\)


XOR:

a^a=0;//自己和自己求异或为0

0^a=a;

abb=a;

(1) 如何求出0123...^n

n 0 1 2 3 4 5 6 7 8 9...

f(n) 0 1 3 0 4 1 7 0 8 1...

观察可知:4个一循环,分别为:

n、1、n+1、0;

(具体结论可以自行查看其二进制数,最后两位是在00,01,10,11循环,对应的就是n,1,n+1,0)

(2) 如何在1,2,3...,n取满足 ai≡ k (mod 2j )的 所有数的 异或和

例如 x ≡ 3 mod 24

x为:

000011,010011,100011,110011,…

可以发现后 i 位对异或和没有有影响,只有前几位有影响

因此先对前面n为求异或和,再对最后i位求异或和

对于前面发现是从0000,0001,0010,0011,增长,运用结论1可以快速求出


#include "bits/stdc++.h"

using namespace std;
#define int long long
#define endl "\n"
#define PII pair<int,int>
#define PIII pair<int,PII>
const int MOD = 1e9 + 7;

bool cmp(PII p1, PII p2) {
    return p1.first + p1.second < p2.first + p2.second;
}

int get(int n) {//0-n求异或
    if (n % 4 == 0) {
        return n;
    }
    if (n % 4 == 1) {
        return 1;
    }
    if (n % 4 == 2) {
        return n + 1;
    }
    if (n % 4 == 3) {
        return 0;
    }
}

int delet_uninterest_get(int n, int i, int k) {
    if (i == 0) {
        if (k == 0)
            return get(n);
        else
            return 0; // 当i=0且k≠0时,无满足条件的x
    }
    int pow2 = 1ULL << i;
    if (n < k)
        return 0;
    int m = (n - k) / pow2;
    int cnt = m + 1;
    int res = get(m) << i;
    if (cnt % 2 == 1)
        res ^= k;
    return res;
}

void slu() {
    int l, r, i, k;
    cin >> l >> r >> i >> k;
    int sum = get(l - 1) ^ get(r);
    int notInterest = (delet_uninterest_get(r, i, k) ^ delet_uninterest_get(l - 1, i, k));
    int res = sum ^ notInterest;
    cout << res << endl;
}

signed main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int T;
    cin >> T;
//    T = 1;
    while (T--)slu();

}

Codeforces Round 984 (Div. 3)

G. Library of Magic

(交互题)二分 数论 位运算


图书馆中共有 \(n\) 种书,每种书有两本,编号 \(1\)\(n\) ,其中有不同的三本 \(a,b,c\) 被偷走,要求通过不超过150次询问找出被偷走的三本书编号:

  • \("xor\ l\ r"\)位XOR查询,参数为 \(l\)\(r\) 。设 \(k\) 为图书馆中编号大于或等于 \(l\) 且小于或等于 \(r\) 的此类书籍的数量。你将得到计算结果 \(v_1 \oplus v_2 \oplus...\oplus v_k\) ,其中 \(v_1...v_k\) 是这些书脊上的数字。也就是说将区间 \([l,r]\) 中所有现存的书籍编号异或在一起。

异或运算基础 XOR Basics

按位异或满足交换律、结合律,另外,异或的逆运算也是异或

  • \(0 \oplus a = a\)
  • \(a \oplus a = 0\)
  • \((a \oplus b) \oplus c = a \oplus (b \oplus c)\)
  • \((a \oplus b) (b \oplus c) = a \oplus c\)
  • \(a \oplus b = c \Rightarrow a \oplus c = b\)
  • \(a \oplus b = c \oplus d \Rightarrow a \oplus c = b \oplus d\)

对于每次询问区间 \(xor\ l \ r\) ,可能存在以下三类情况

  1. 区间中有一个未知数 \(a\)

    此时询问结果即为 \(a\)

  2. 区间中有两个未知数 \(a,b\)

​ ① \(a,b\) 都分布在区间 \(l,mid\)\(mid,r\) 其中一个部分。此时 \(xor\ l\ mid = a \oplus b \ne 0\)\(xor\ mid\ r = 0\)\(xor\ l\ mid = 0\)\(xor\ mid\ r = a \oplus b \ne 0\) 我们只需要对不为零的区间继续二分即可。便捷的方法在代码部分讲解。

​ ② \(a,b\) 各分布在区间 \(l,mid\)\(mid,r\) 。此时只需要一次二分就能来到第一种情况。

  1. 区间中有三个未知数 \(a,b,c\)

    前两种情况都比较明了,易错点主要在第三部分。

    \(a \oplus b \oplus c \ne 0\) 这种情况也比较简单,无论三个数出现在 \(l,mid\)\(mid,r\) 哪个区间,总能通过不断二分将它化成第1、2种情况来解决。

    \(a \oplus b \oplus c = 0\) 这种情况就比较复杂了,我们无法通过判 \(0\) 的方式准确找到未知数所在区间。

    但对于这种特殊情况,我们举个例子:

\(a\) :1 0 1 0 0 1

\(b\) : 1 0 0 0 0 1

\(c\) : 0 0 1 0 0 0

​ 对于每一位,有两种情况:三个数为 \(0\) 或两个 \(1\) 一个 \(0\) ,且对于三个数的最高位来说,只能是后者,也就是说有且只有两个数,在最高位上有 \(1\) 。那么我们就可以通过一个每一位上都是 \(1\) 的数 \(A\) ,将区间分为 \([l,A] [A,r]\)两部分,将这三个数分开。例如上面的例子中,

\(a\) :1 0 1 0 0 1

\(b\) : 1 0 0 0 0 1

\(A\) : 1 1 1 1 1

\(c\) : 0 1 0 0 0

​ 这时对两个区间进行第1、2种情况中的操作即可。


auto find(int l, int r, int x) {
  while (l < r - 1) {
    int mid = (l + r) >> 1;
    if (ask(1, mid) == x) //只要左半部分询问值为x,就砍掉左半部分
      l = mid;
    else
      r = mid;
  }
  return r;
}

函数实现功能为在区间 \(l,r\) 中找到最小的且大于 \(x\) 的未知数。这样最小值与次小值都可以通过一次函数操作精准找出,不需要那么多组特判。


#include <bits/stdc++.h>
using namespace std;

#define int long long

int T;
int n;

auto ask(int l, int r) {
  cout << "xor " << l << " " << r << endl;
  fflush(stdout);
  int x;
  cin >> x;
  return x;
}

auto find(int l, int r, int x) {
  while (l < r - 1) {
    int mid = l + r >> 1;
    if (ask(1, mid) == x)
      l = mid;
    else
      r = mid;
  }
  return r;
}

void sol() {
  cin >> n;

  int quelr = ask(1, n);
  int a, b, c;
  if (quelr == 0) {
    int cnt = 0;
    do {
      ++cnt;
      a = ask(1, (1ll << cnt) - 1);
    } while (a == 0);
  } else
    a = find(0, n, 0); //

  b = find(a, n, a);
  c = quelr ^ a ^ b;

  cout << "ans " << a << " " << b << " " << c << endl;
  fflush(stdout);
}

signed main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  cin >> T;
  while (T--) {
    sol();
    fflush(stdout);
  }
  return 0;
}
posted @ 2024-11-05 23:04  Sure042  阅读(71)  评论(0编辑  收藏  举报