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\) 。
-
n*k<=10^5 可以考虑使用vector存二维数组
-
a | b >= max(a,b) 按位或只会增大,不会减小,很容易证明
-
直接暴力 复杂度为 q*n*k 到达亿级别 不予考虑
-
如何优化?单调递增!直接考虑二分!
#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\) ,可能存在以下三类情况
-
区间中有一个未知数 \(a\)
此时询问结果即为 \(a\)
-
区间中有两个未知数 \(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\) 。此时只需要一次二分就能来到第一种情况。
-
区间中有三个未知数 \(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;
}