1 条题解

  • 0
    @ 2025-5-6 20:24:35

    首先声明一个概念:msb(n)msb(n)表示:nn的二进制的最高有效位,即nn的二进制表示的最高一位11。比如msb(1101)2=3,msb(1)=0msb(1101)_2 = 3,msb(1) = 0

    首先, 对于两条鱼:

    • 如果msb(a)>msb(b)msb(鱼a)>msb(鱼b)a鱼a一定能吃掉b鱼b,并且吃掉b鱼bmsb(a)msb(鱼a)一定不会减少(10=01 \bigoplus 0 = 0)。
    • 如果msb(a)<msb(b)msb(鱼a)<msb(鱼b)a鱼a一定不能吃掉b鱼b

    需要注意的就是msb(a)=msb(b)msb(鱼a)=msb(鱼b)的情况,如果该情况a鱼a能吃掉b鱼b,则吃掉b鱼bmsb(a)msb(鱼a)一定会减少(11=01 \bigoplus 1 = 0 )。

    在任何时候,我们都应该尽可能多地吃掉左边 msbmsb较小的鱼,也就是只要msbmsb小就一直吃,而如果一个一个判断,时间复杂度肯定是不够的 ,所以要用预处理的思想,用一个lastlast数组来预处理一下,left[i][j]left[i][j]表示ii前面(包括ii)最后一条(也就是离ii最近的那条)msbjmsb \ge j的鱼的下标。如果当前xx在位置ii,在iileft[i][msb(x)]left[i][msb(x)]之间的鱼肯定都可以吃,直接将xx移到left[i][mbs(x)]left[i][mbs(x)]的位置,并让xx异或上两条鱼之间的每条鱼的重量即可,而直接一个异或也一定会超时,需要预处理一下区间的异或和,区间问题就很容易想到前缀和,开一个prepre数组,pre[i][j]pre[i][j]就表示“从这条鱼ii开始,还没遇到下一个msbjmsb \ge j 的鱼”,即从第left[i][j]+1left[i][j] + 1条鱼~第ii条鱼的前缀异或和。而对于msbmsb大的鱼,直接breakbreak就可以了。对于msbmsbmsb(x)msb(x)相等的鱼,每次判断一下它的重量和xx哪个大,如果wxw \le x就吃掉,否则就breakbreak,因为msbmsb一直会减少,所以最多只会执行log(x)log(x)次运算,时间复杂度可以通过。

    接下来就是思考如何预处理leftleftprepre数组:计当前msb(a[i])=dmsb(a[i]) = d,从00~3030遍历可能的每一位jj

    • 对于leftleft数组:
      • 如果jdj \le d,左边第一个msbjmsb \ge j的位置就是iileft[i][j]=ileft[i][j] = i
      • 如果j>dj > d,就递推,left[i][j]=left[i1][j]left[i][j] = left[i - 1][j]
    • 对于prepre数组:
      • 如果jdj \le dleft[i][j]=ileft[i][j] = i,区间中没有鱼,pre[i][j]=0pre[i][j] = 0
      • 如果j>dj > d,区间中加了条鱼iipre[i][j]=w[i];pre[i][j] \bigoplus= w[i];

    预处理完后,从第nn条鱼开始从右向左模拟即可。

    时间复杂度:接近O((n+q)×log(maxw))O(\sum(n + q) \times log(max w))

    #include <iostream>
    #include <algorithm>
    #include <vector>
    #define endl '\n'
    using namespace std;
    
    void solve(){
    	int n, q;
    	cin >> n >> q;
    	vector<int> w(n);
    	for(int i = 0; i < n; i++){
    		cin >> w[i];
    	}
    	vector<vector<int>> left(n, vector<int> (31, -1));
    	vector<vector<int>> pre(n, vector<int> (31, 0));
    	for(int i = 0; i < n; i++){
    		if(i > 0){
    			left[i] = left[i - 1];
    			pre[i] = pre[i - 1];
    		}
    		int d = __lg(w[i]);
    		for(int j = 0; j <= d; j++){
    			left[i][j] = i;
    			pre[i][j] = 0;
    		}
    		for(int j = d + 1; j <= 30; j++){
    			pre[i][j] ^= w[i];
    		}
    	}
    	while(q--){
    		int x;
    		cin >> x;
    		int j = n - 1;
    		int res = 0;
    		while(x > 0 && j >= 0){
    			int d = __lg(x);
    			int k = left[j][d];
    			x ^= pre[j][d];
    			j = k;
    			if(j == -1) break;
    			if(x < w[j]) break;
    			x ^= w[j];
    			j--;
    		}
    		res = n - 1 - j;
    		cout << res << ' ';		
    	}
    	cout << endl;
    }
    
    int main(){
    	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    	int T;
    	cin >> T;
    	while(T--){
    		solve();
    	}
    	return 0;
    }
    
    • 1

    信息

    ID
    2275
    时间
    1000ms
    内存
    256MiB
    难度
    9
    标签
    (无)
    递交数
    13
    已通过
    2
    上传者