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;
}