Codeforces Round 984 (Div. 3) 題解

Sure042發表於2024-11-05

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

相關文章