2025牛客寒假算法基础集训营4 题解

Preface

又来了,后面两场要缺席了,旅游去了。依旧是从大概2个小时才开始打的。

我会在代码一些有必要的地方加上注释,签到题可能一般就不会写了.

以下是代码火车头:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
#include <queue>
#include <map>
#include <unordered_map>
#include <iomanip>
#define endl '\n'
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define rep2(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;

template<typename T>
void cc(const vector<T> &tem) {
    for (const auto &x: tem) cout << x << ' ';
    cout << endl;
}

template<typename T>
void cc(const T &a) { cout << a << endl; }

template<typename T1, typename T2>
void cc(const T1 &a, const T2 &b) { cout << a << ' ' << b << endl; }

template<typename T1, typename T2, typename T3>
void cc(const T1 &a, const T2 &b, const T3 &c) { cout << a << ' ' << b << ' ' << c << endl; }

void cc(const string &s) { cout << s << endl; }

void fileRead() {
#ifdef LOCALL
    freopen("D:\\AADVISE\\Clioncode\\untitled2\\in.txt", "r", stdin);
    freopen("D:\\AADVISE\\Clioncode\\untitled2\\out.txt", "w", stdout);
#endif
}

void kuaidu() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); }

inline int max(int a, int b) {
    if (a < b) return b;
    return a;
}

inline double max(double a, double b) {
    if (a < b) return b;
    return a;
}

inline int min(int a, int b) {
    if (a < b) return a;
    return b;
}

inline double min(double a, double b) {
    if (a < b) return a;
    return b;
}

void cmax(int &a, const int &b) { if (b > a) a = b; }
void cmin(int &a, const int &b) { if (b < a) a = b; }
void cmin(double &a, const double &b) { if (b < a) a = b; }
void cmax(double &a, const double &b) { if (b > a) a = b; }
using PII = pair<int, int>;
using i128 = __int128;
using vec_int = std::vector<int>;
using vec_char = std::vector<char>;
using vec_double = std::vector<double>;
using vec_int2 = std::vector<std::vector<int> >;
using que_int = std::queue<int>;

Problem A. Tokitsukaze and Absolute Expectation

首先我们先考虑一下比较简单的情况,如果只有两个数字,区间已知,我们该如何计算?

在这个比较简单的情况我们再继续思考,如果这两个区间是不相交的,我们该如何计算?如果这两个区间是相交的我们又该如何计算?

可以来说一下,如果两个区间是不相交的,对于只考虑两个数。我们该如何计算?其实我们用暴力的方法把每一项都写下来之后,然后再合并,我们可以读不出来。我们把比较小的那个区间称作是区间A,大的那个区间称作是区间B。设sum是一个区间的所有的数字之和,len这个区间的长度大小。

那么他们的贡献就是sumBlenAsumAlenB

那么相交的区间我们应该怎么考虑呢?其实也很简单,我们只需要把每一块儿区间开开来计算两两组合枚举推算就可以了。比如区间a和区间b是相交的,从左到右设立区间Q,W,E。那么我们只需要计算QW,QE,WW,WE的答案就好了。

这是只看两个数的情况,对于有很多的数字,我们只需要从左到右来处理就好啦。就像每一次把当前的数字看做是一个集合,左边我们遍历过的数字是一个集合,而现在是要去合并他们。

用v代表一个集合里的发生的差值之和,c代表集合里的区间大小的乘积。

			v1 = v1 * c2 % mod + v2 * c1 % mod;
			v1 %= mod;
			c1 *= (A[i - 1].second - A[i - 1].first + 1);
			c1 %= mod;

转移方程长这个样子

//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//struct or namespace:


//--------------------------------------------------------------------------------

int dfs(int l1,int r1,int l2,int r2) {
	if (l1 > r1 or l2 > r2) return 0;
	if (l1 > l2) {
		swap(l1, l2);
		swap(r1, r2);
	}
	else {
		if (l1 == l2) {
			if (r1 > r2) {
				swap(l1, l2);
				swap(r1, r2);
			}
		}
	}
	int ans = 0;
	auto kuai = Kuai<mod>;

	if (l1 == l2 and r1 == r2) {
		int len = r1 - l1 + 1;
		len -= 1;
		int v1 = len * (len + 1) % mod * (len + len + 1) % mod * kuai(6, mod - 2) % mod;
		int v2 = (len + 1) * len % mod * kuai(2, mod - 2) % mod;
		// ans += len * (len + 1) * (len + len + 1) / 6;
		// ans += (len + 1) * len / 2;
		ans = v1 + v2;
		ans %= mod;
		return ans;
	}

	int n1 = r1 - l1 + 1, n2 = r2 - l2 + 1;
	int sum1 = (l1 + r1) % mod * n1 % mod * kuai(2, mod - 2) % mod;
	int sum2 = (l2 + r2) % mod * n2 % mod * kuai(2, mod - 2) % mod;
	int aaa = n1 * sum2 % mod - n2 * sum1 % mod;
	aaa %= mod;
	aaa += mod;
	aaa %= mod;
	ans = aaa;
	return ans;
}

int dfs(const PII &q1, const PII &q2) {
	int ans = 0;
	ans = dfs(q1.first, q1.second, q2.first, q2.second);
	return ans % mod;
}

int solve(int l1,int r1,int l2,int r2) {
	if (l1 > l2) {
		swap(l1, l2);
		swap(r1, r2);
	}
	else {
		if (l1 == l2) {
			if (r1 > r2) {
				swap(l1, l2);
				swap(r1, r2);
			}
		}
	}

	if (l1 == l2 and r1 == r2) {
		return dfs(l1, r1, l2, r2);
	}

	if (r1 < l2) {
		return dfs(l1, r1, l2, r2);

	}

	if (l1 <= l2 and r2 < r1) {
		PII q1, q2, q3;
		q1 = {l1, l2 - 1};
		q2 = {l2, r2};
		q3 = {r2 + 1, r1};
		int ans = 0;
		ans += dfs(q1, q2);
		ans %= mod;
		ans += dfs(q2, q2);
		ans %= mod;

		ans += dfs(q3, q2);
		ans %= mod;

		return ans;
	}

	if (l2 <= r1 and r1 <= r2) {
		PII q1, q2, q3;
		q1 = {l1, l2 - 1};
		q2 = {l2, r1};
		q3 = {r1 + 1, r2};
		int ans = 0;
		ans += dfs(q1, q2);
		ans %= mod;

		ans += dfs(q1, q3);
		ans %= mod;

		ans += dfs(q2, q2);
		ans %= mod;

		ans += dfs(q2, q3);
		ans %= mod;

		return ans;
	}

	return dfs(l1, r1, l2, r2);

}

int solve(const PII &q1, const PII &q2) {
	return solve(q1.first, q1.second, q2.first, q2.second);
}

PII A[N];

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	cin >> T;
	while (T--) {
		cin >> n;
		int nums = 1;

		rep(i, 1, n) {
			cin >> A[i].first;
			cin >> A[i].second;
			nums *= (A[i].second - A[i].first + 1);
			nums %= mod;
		}

		int v1, c1, v2, c2;
		c1 = A[1].second - A[1].first + 1;
		v1 = solve(A[1], A[2]);
		// cc(v1);
		rep(i, 3, n) {
			c2 = A[i].second - A[i].first + 1;
			v2 = solve(A[i - 1], A[i]);

			// cc(i, c1, v1);

			v1 = v1 * c2 % mod + v2 * c1 % mod;
			v1 %= mod;
			c1 *= (A[i - 1].second - A[i - 1].first + 1);
			c1 %= mod;
			// cc(v1);
		}
		auto kuai = Kuai<mod>;
		if (nums == 0) {
			cout << "sadasdaas" << endl;
			continue;
		}
		int ans = v1 * kuai(nums, mod - 2) % mod;
		// cc(v1);
		cc(ans);

	}
	return 0;
}

/*
1
5
3 4
2 5
2 9
1 1
10 14

*/

Problem B. Tokitsukaze and Balance String(easy)

请转战到C题

Problem C. Tokitsukaze and Balance String (hard)

多写几组数据找找规律,就会发现抛开两侧的不谈,剩余的中间的部分所做的贡献是一样的并且都是固定的,如果设除去两侧,剩余的部分的问号的个数是c,那么他们的贡献的和便是kuai(2, c) * (s.size() - 2),也就是说每一个位置的贡献是2的c次方。然后再考虑上两侧的四种情况,分情况去讨论输出答案就好了。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//struct or namespace:

auto kuai = Kuai<mod>;
//--------------------------------------------------------------------------------

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	cin >> T;
	while (T--) {
		string s;
		cin >> n;
		cin >> s;

		if (n == 1) {
			if (s == "?") cc(2);
			else cc(1);
			continue;
		}


		int c = 0;
		for (auto &x: s) {
			if (x == '?') c += 1;
		}
		// c -= 2;
		int len = s.size();

		if (s[0] == '?') c -= 1;
		if (s[len - 1] == '?') c -= 1;
		int ans = 0;

		if (s[0] == '?' and s[len - 1] == '?') {
			ans += 2 * kuai(2, c) % mod * s.size() % mod;
		}
		else if (s[0] != '?' and s[len - 1] != '?') {
			if (s[0] == s[len - 1]) {
				ans += kuai(2, c) * (s.size() - 2) % mod;
			}
			else {
				ans += 2 * kuai(2, c) % mod;
			}
		}
		else {
			ans += kuai(2, c) % mod * s.size() % mod;
		}


		cc(ans);


	}
	return 0;
}

/*


*/

Problem D. Tokitsukaze and Concatenate‌ Palindrome

首先将长度比较长的设成字符串a,短的是字符串b。

先考虑一种简单的情况。如果b是a的子集,那直接a去掉b的所剩余的字符集中把每一个字符的个数%2,再求和,最后/2(向下取整,相当于我们改掉那一半的个数,便能得到回文串了)便是答案。那如果不是呢?

那么如果a不是完全包于b。也就是说如果b中间有一些字符a没有的话,那么我们是一定要加上这个部分的贡献的。我们设这一部分的贡献是sum2。那记录下来字符串a中已经去除了b的部分的剩余的字符的数%2的和,设这个是sum。那先考虑当sum是大于sum2的情况下,既然我们已经先要选择当sum2个字母改变了,那现在还差sums-sum2个字母去得到回文串,所以我们就还要再加上这个差值的一半,修改差值的一半的字母。也能够得到一个完整的回文串。

那如果是小于的情况呢,那显然我们就根本就不需要再操作就好了。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
map<char,int> mp, mp2;
//--------------------------------------------------------------------------------
//struct or namespace:

//--------------------------------------------------------------------------------

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	cin >> T;
	while (T--) {
		cin >> n >> m;
		string s1, s2;
		cin >> s1 >> s2;
		mp.clear();
		mp2.clear();
		if (n < m) {
			swap(n, m);
			swap(s1, s2);
		}
		for (auto &x: s1) {
			mp[x] += 1;
		}
		for (auto &x: s2) {
			mp[x] -= 1;
		}
		int sum = 0, sum2 = 0;

		for (auto [a,b]: mp) {
			if (b < 0) {
				sum2 += -b;
			}
		}
		int ans = 0;
		ans += sum2;

		for (auto [a,b]: mp) {
			if (b < 0) continue;
			sum += b % 2;
		}
		if (sum >= sum2) {
			ans += (sum - sum2) / 2;
		}
		else {
		}
		cc(ans);
	}
	return 0;
}

/*


*/

Problem E. Tokitsukaze and Dragon's Breath

先把每一个左对角线的全局之和搜索到,然后再将其赋值到每一个坐标中。右对角线也是这样子去做。

那么我们就得到了对于当前这一个点来说展开的左对角线之和和右对角线之和分别是多少。

那么我们加上这两个权值再减去当前这个点便是这个点的贡献取出贡献的最大值就好了

//--------------------------------------------------------------------------------
const int N = 1e3 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
int A[N][N];
int you[N][N]; // /
int zuo[N][N]; // \
//--------------------------------------------------------------------------------
//struct or namespace:
 
//--------------------------------------------------------------------------------
int dfs(int x,int y,int dx,int dy) {
    if (x < 1 or y < 1 or x > n or y > m) return 0;
    int val = 0;
    val += dfs(x + dx, y + dy, dx, dy);
    val += A[x][y];
    return val;
}
 
void fuzhi(int x,int y,int dx,int dy,int zong) {
    if (x < 1 or y < 1 or x > n or y > m) return;
    fuzhi(x + dx, y + dy, dx, dy, zong);
    if (dx == 1 and dy == 1) zuo[x][y] = zong;
    else you[x][y] = zong;
}
 
signed main() {
    fileRead();
    kuaidu();
    T = 1;
    cin >> T;
    while (T--) {
        cin >> n >> m;
        rep(i, 1, n)
            rep(j, 1, m) {
                cin >> A[i][j];
                zuo[i][j] = 0;
                you[i][j] = 0;
            }
 
        rep2(j, m, 1) {
            int val = dfs(1, j, 1, 1);
            fuzhi(1, j, 1, 1, val);
        }
        rep(i, 2, n) {
            int val = dfs(i, 1, 1, 1);
            fuzhi(i, 1, 1, 1, val);
        }
        rep(j, 1, m) {
            int val = dfs(1, j, 1, -1);
            fuzhi(1, j, 1, -1, val);
        }
        rep(i, 2, n) {
            int val = dfs(i, m, 1, -1);
            fuzhi(i, m, 1, -1, val);
        }
        int ans = -INF;
        rep(i, 1, n) {
            rep(j, 1, m) {
                cmax(ans, zuo[i][j] + you[i][j] - A[i][j]);
            }
        }
        cc(ans);
    }
    return 0;
}

Problem F. Tokitsukaze and Kth Problem (easy)

非常典的一道题,只能说没有时间去做了,有点可惜。忘了是在哪里,之前的牛客周赛上还是在哪里有出过这种类似的题。

我们先对他取模p那么任意两个数的和一定会小于2p。我们的做法就先把原本的数组取模然后从小到大排个序。就可以用双指针的方法去解决这一类问题。

首先我们先用二分找到第k大的数是几,然后再用双指针去搜索,将其每一个遍历放到vec ans即可最后直接输出就好了。

	rep(i, 1, n) {
		while (r1 and A[i] + A[r1] >= r) r1--;
		while (r2 and A[i] + A[r2] >= r + mod) r2--;
		rep(j, max(i+1,r1+1), n) {
			if (A[i] + A[j] >= mod) break;
			ans.push_back(A[i] + A[j]);
		}
		rep(j, max(i+1,r2+1), n) {
			ans.push_back((A[i] + A[j]) % mod);
		}
	}

这个里面嵌套的for循环是存储答案的。

关于这个时间复杂度的证明,因为k<=2e5,我们在这一部分执行也会小于等于k,所以时间复杂度就不会是N方级别的,就没有问题。

//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
// const int mod = 1e9 + 7;
int mod;
const int INF = 1e16;
int n, m, T;
int k;
int A[N];

//--------------------------------------------------------------------------------
//struct or namespace:

//--------------------------------------------------------------------------------

//特判全都输出的情况
void out() {
	vec<int> ans;
	rep(i, 1, n) {
		rep(j, i+1, n) {
			ans.push_back((A[i] + A[j]) % mod);
		}
	}
	while (ans.size() < k) ans.push_back(-1);
	sort(ans.begin(), ans.end(), [](int a, int b) { return a > b; });
	cc(ans);
}

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	cin >> T;
	while (T--) {
		cin >> n >> mod >> k;
		rep(i, 1, n) {
			cin >> A[i];
			A[i] %= mod;
		}
		sort(A + 1, A + n + 1);

		if (n * (n - 1) / 2 <= k) {
			out();
			continue;
		}

		auto get = [&](int x)-> int {
			int ans = 0;
			int r = n;
			rep(i, 1, n) {
				while (r and A[i] + A[r] >= x) r--;
				if (r > i) ans += n - r;
				else ans += n - i;
			}
			return ans;
		};
		//大于等于mid的个数
		auto check = [&](int mid)-> int {
			return get(mid) + get(mid + mod) - get(mod);
		};

		int l = 0, r = mod;
		while (l + 1 != r) {
			int mid = (l + r) >> 1;
			if (check(mid) >= k) l = mid;
			else r = mid;
		}

		// cc(l);

		vec<int> ans;
		int r1 = n, r2 = n;
		rep(i, 1, n) {
			while (r1 and A[i] + A[r1] >= r) r1--;
			while (r2 and A[i] + A[r2] >= r + mod) r2--;
			rep(j, max(i+1,r1+1), n) {
				if (A[i] + A[j] >= mod) break;
				ans.push_back(A[i] + A[j]);
			}
			rep(j, max(i+1,r2+1), n) {
				ans.push_back((A[i] + A[j]) % mod);
			}
		}

		sort(ans.begin(), ans.end(), [](int a, int b) { return a > b; });
		while (ans.size() < k) ans.push_back(l);
		cc(ans);
		// cc(ans2);


	}
	return 0;
}

/*


*/

Problem I. Tokitsukaze and Pajama Party

我觉得这个题并没有那么的签到。 根据题意,我们每一次找到后面这个长串的字符串的时候就往前找有多少一个u字符就可以了,但是注意要去掉相邻的。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
 
//--------------------------------------------------------------------------------
//struct or namespace:
 
//--------------------------------------------------------------------------------
 
signed main() {
    fileRead();
    kuaidu();
    T = 1;
    cin >> T;
    while (T--) {
        cin >> n;
        string s;
        cin >> s;
        int sum = 0;
        int ans = 0;
        rep(i, 0, n-1) {
            string s1 = "";
            rep(j, 0, 7) {
                if (i + j <= n - 1)
                    s1 = s1 + s[i + j];
            }
            if (s1 == "uwawauwa") {
                ans += sum;
                if (i - 1 >= 0 and s[i - 1] == 'u') ans -= 1;
            }
            if (s[i] == 'u') sum++;
 
        }
        cc(ans);
 
    }
    return 0;
}

Problem J. Tokitsukaze and Recall

终于出图论了,难度应该是有铜弱难度的。

大概的思路是,我们可以花费一代价将一个并查集里面的点全部都访问到。所以我们按理来说应该要先将并查集根据大小从大到小排列,然后优先遍历个数大的,但是我们并查集中还要维护一个最小的编号。当个数相同的时候就优先遍历编号小的那一个。但是这样子做的话样例会有过不去的,因为如果我们有多余的价值,我们可以去优先访问到我们没有访问到的最小编号,就例如一号编号和二号编号在同一个并查集。 假设我们有多余的代价,我们就可以在访问一号编号之后去强制访问二号编号。直接将这个特殊判断写到我们的 BFS 当中就好了。

有一个细节注意:我们不能直接将剩余的代价从编号一到N依次遍历,如果没有放入我们的队列中我们就把它放进去。最后直接跑一个普通BFS。

这样的做法是错误的,因为如果一号编号和二号编号它们是相邻的情况下这样的做法将不对。

//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//struct or namespace:

namespace z {
	struct ED {
		int y;
		int val;
	};

	vector<ED> A[N];
	int son[N], dep[N];

	void clear(const int &n) {
		rep(i, 1, n) {
			A[i].clear();
		}
	}

	void add(const int &x, const int &y, int c = 1) {
		A[x].push_back({y, c});
	}
};

class DSU {
	struct Info {
		int fa;
		int siz;
		int mmin;
	} dsu[N];

public:
	Info &operator()(const int &x) { return dsu[find(x)]; }
	Info &operator[](const int &x) { return dsu[x]; }

	void clear(int n) {
		//TODO 初始化
		rep(i, 1, n) dsu[i].fa = i, dsu[i].siz = 1, dsu[i].mmin = i;
	}

	void merge(int x, int y) {
		x = find(x), y = find(y);
		if (x == y) return;
		dsu[y].fa = dsu[x].fa;
		if (dsu[x].siz < dsu[y].siz) swap(dsu[x], dsu[y]);
		dsu[y].fa = dsu[x].fa, dsu[x].siz += dsu[y].siz;
		cmin(dsu[x].mmin, dsu[y].mmin);
		//TODO 合并操作

	}

	int find(int x) { return x == dsu[x].fa ? x : dsu[x].fa = find(dsu[x].fa); }
	bool same(int x, int y) { return (find(x) == find(y)); }
} dsu;

int k;
bool vis[N], vis2[N];
//--------------------------------------------------------------------------------

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	cin >> T;
	while (T--) {
		cin >> n >> m >> k;
		dsu.clear(n);
		z::clear(n);
		rep(i, 1, n) {
			vis[i] = 0;
			vis2[i] = 0;
		}
		rep(i, 1, m) {
			int a, b;
			cin >> a >> b;
			dsu.merge(a, b);
			z::add(a, b);
			z::add(b, a);
		}
		vec<PII> tem;
		rep(i, 1, n) {
			if (dsu.find(i) == i) {
				tem.push_back({dsu[i].siz, dsu[i].mmin});
			}
		}
		sort(tem.begin(), tem.end(), [](const PII &q1, const PII &q2) {
			if (q1.first == q2.first) return q1.second < q2.second;
			return q1.first > q2.first;
		});

		priority_queue<int> F;
		int cnt = 0;
		for (auto &[siz,x]: tem) {
			cnt++;
			if (cnt <= k) {
				F.push(-x);
				vis[x] = 1;
			}
		}
		k -= cnt;
		cmax(k, 0);
		vec<int> ans;
		int now = 0;
		while (!F.empty()) {
			auto x = F.top();
			x = -x;

			if (abs(now - x) > 1 and k) {
				k--;
				F.push(-(now + 1));
				continue;
			}

			now = x;
			F.pop();
			if (vis2[x]) continue;
			vis2[x] = 1;
			ans.push_back(x);
			for (auto &[y,val]: z::A[x]) {
				if (vis2[y]) continue;
				F.push(-y);
			}
		}
		cc(ans.size());
		cc(ans);


	}
	return 0;
}

/*


*/

Problem K. Tokitsukaze and Shawarma

纯纯签到题

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//struct or namespace:

//--------------------------------------------------------------------------------

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	cin >> T;
	while (T--) {
		int x, y, z, a, b, c;
		cin >> x >> y >> z >> a >> b >> c;
		int mmax = 0;
		cmax(mmax, a * x);
		cmax(mmax, b * y);
		cmax(mmax, c * z);
		cc(mmax);

	}
	return 0;
}

/*


*/

Problem L. Tokitsukaze and XOR-Triangle

数据结构题,非常典型的一个trick就是按位去考虑。考虑二进制的形式,将每一个数拆成三十个位置,对于每一个位置分开计算,最后在将答案加起来就好了。

那么接下来每一个数字将会是零或者是一,这个题就变成了简单的查询区间内0,1的个数和。

用前缀和的形式去做就好了,具体的式子就写到代码里了。

		de = getb0(j, l, r) * geta1(j, 1, l - 1) % mod + getb1(j, l, r) * geta0(j, 1, l - 1) % mod;
		de %= mod;
		int tem = (ans[j][r] - ans[j][l - 1] - de) % mod + mod;

get函数是求出二进制中第j位区间内0或者1的个数的和。

贡献是tem。

//--------------------------------------------------------------------------------
const int N = 3e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;

//--------------------------------------------------------------------------------
//struct or namespace:
int pre_a0[32][N];
int pre_a1[32][N];
int pre_b0[32][N];
int pre_b1[32][N];
int ans[32][N];
int A[N], B[N];

int geta0(int k,int l,int r) {
	return pre_a0[k][r] - pre_a0[k][l - 1];
}

int geta1(int k,int l,int r) {
	return pre_a1[k][r] - pre_a1[k][l - 1];
}

int getb0(int k,int l,int r) {
	return pre_b0[k][r] - pre_b0[k][l - 1];
}

int getb1(int k,int l,int r) {
	return pre_b1[k][r] - pre_b1[k][l - 1];
}

int solve(int l,int r) {
	int res = 0;
	rep(j, 0, 30) {
		int de = 0;
		de = getb0(j, l, r) * geta1(j, 1, l - 1) % mod + getb1(j, l, r) * geta0(j, 1, l - 1) % mod;
		de %= mod;
		int tem = (ans[j][r] - ans[j][l - 1] - de) % mod + mod;
		tem %= mod;
		res += tem * (1ll << j) % mod;
		res %= mod;
	}
	return res;
}

//--------------------------------------------------------------------------------

signed main() {
	fileRead();
	kuaidu();
	T = 1;
	cin >> T;
	while (T--) {
		int q;
		cin >> n >> q;
		rep(i, 1, n) {
			cin >> A[i];
		}
		rep(i, 1, n) {
			cin >> B[i];
		}

		rep(j, 0, 30) {
			rep(i, 1, n) {
				pre_a0[j][i] = 0;
				pre_a1[j][i] = 0;
				pre_b0[j][i] = 0;
				pre_b1[j][i] = 0;
				ans[j][i] = 0;
			}
		}

		rep(j, 0, 30) {
			rep(i, 1, n) {
				pre_a0[j][i] += pre_a0[j][i - 1];
				if (A[i] >> j & 1) pre_a0[j][i] += 0;
				else pre_a0[j][i] += 1;

				pre_a1[j][i] += pre_a1[j][i - 1];
				if (A[i] >> j & 1) pre_a1[j][i] += 1;

				pre_b0[j][i] += pre_b0[j][i - 1];
				if (B[i] >> j & 1) pre_b0[j][i] += 0;
				else pre_b0[j][i] += 1;

				pre_b1[j][i] += pre_b1[j][i - 1];
				if (B[i] >> j & 1) pre_b1[j][i] += 1;
			}
		}

		rep(j, 0, 30) {
			rep(i, 1, n) {
				ans[j][i] += ans[j][i - 1];
				ans[j][i] %= mod;

				if (B[i] >> j & 1) {
					ans[j][i] += geta0(j, 1, i);
					ans[j][i] %= mod;
				}
				else {
					ans[j][i] += geta1(j, 1, i);
					ans[j][i] %= mod;

				}
			}
		}

		rep(i, 1, q) {
			int l, r;
			cin >> l >> r;
			cc(solve(l, r));
		}

	}
	return 0;
}

/*


*/

PostScript

阿巴巴阿巴

posted @   AdviseDY  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
点击右上角即可分享
微信分享提示