[CSP模拟]异或帽子+传话游戏+全球覆盖+幂次序列
异或帽子( hat )
【题目描述】
小黑的老师给他们班的同学做了一个小游戏。老师给班里\(n\)个同学(由于小黑班上男女生人数一样,所以\(n\)一定是偶数)每人戴上了一顶帽子,第\(i\)个同学的帽子上写有一个数字\(A_i\) 。每个人只能看到其他人帽子上的数字,而看不到自己的。每个同学都把其他所有人的数字的二进制异或和算了出来,并记录了下来,第\(i\)个同学记下来的数字是\(B_i\) 。根据这些消息,老师让同学们试着算出自己帽子上的数字,小黑很聪明,所以一下子就算出来了。
回家的路上,小黑向小白说了这个游戏,并只把所有的\(B_i\) 告诉了小白,让小白猜出来每个人的数字\(A_i\)是多少。但是小白不会算,所以她来向你求助,请你告诉她所有\(A_i\)的值。
【输入格式】
从文件 hat.in 中读入数据。
第一行一个整数\(n\),表示小黑班上有\(n\)个同学,保证是偶数。
接下来一行\(n\)个数字,第\(i\)个数字表示\(B_i\)。
【输出格式】
输出到文件 hat.out 中。
一行\(n\)个数字,第\(i\)个数字表示你求出来的\(A_i\)。
【样例 1 输入】
4
20 11 9 24
【样例 1 输出】
26 5 7 22
【样例 1 解释】
我们用⊕表示异或运算
5 ⊕ 7 ⊕ 22 = 20
26 ⊕ 7 ⊕ 22 = 11
26 ⊕ 5 ⊕ 22 = 9
26 ⊕ 5 ⊕ 7 = 24
【样例 2 】
见下发文件中的hat2.in和hat2.ans。
【数据范围】
对于前10% 的数据,\(n \leq 5\)。
对于前30% 的数据,\(n \leq 5000\)。
对于另10% 的数据,保证\(A_i,B_i \leq 32\)。
对于100% 的数据,\(n \leq 200000,0 \leq A_i , B_i \leq 2^{30}\) 。
「签到题,没什么好说的,不过我的思路好像有亿点麻烦,不过也无所谓了,正解是先求所有Bi的异或和,再用这个异或和跟Bi异或就得到了Ai」
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 200000 + 10;
inline int read() {
int s = 0, w = 1;
char c = getchar();
while (c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
while (c >= '0' && c <= '9') s = s * 10 + c - '0', c = getchar();
return s * w;
}
int A[maxn], B[maxn];
int main() {
freopen("hat.in", "r", stdin);
freopen("hat.out", "w", stdout);
int n = read();
int maxx = 0;
for (int i = 1; i <= n; i++) {
B[i] = read();
maxx = max(maxx, B[i]);
}
int pos = 0;
while (maxx) {
pos++;
maxx >>= 1;
}
for (int now = 0; now < pos; now++) {
int cnt = 0;
for (int i = 1; i <= n; i++) if (!(B[i] & (1 << now))) cnt++;
if (cnt & 1) {
for (int i = 1; i <= n; i++) {
if (!(B[i] & (1 << now))) A[i] += (1 << now);
}
}
else {
for (int i = 1; i <= n; i++) {
if (B[i] & (1 << now)) A[i] += (1 << now);
}
}
}
for (int i = 1; i <= n; i++) {
printf("%d ", A[i]);
}
return 0;
}
传话游戏( string )
【题目描述】
小白和小黑在做一个游戏。小白先在纸条上写下一个由n个只包含小写字母的单词组成的句子,让小黑把它记下来,然后小白收回纸条,小黑凭着记忆向小白复述这个句子。小白发现小黑的记忆力不是很好,经常把单词的顺序记错,但凭借脑海中的印象,小黑复述的句子一定也包含n个只包含小写字母的单词,且其组成的可重集合与原来的句子完全相同。同时,对于某个单词S,其在原句子和小黑复述的句子中第i次出现的位置不会相差超过1。小白很好奇,小黑可能复述出多少种不同的句子,因此找你来帮忙计算。
两个句子被认为是不同的,当且仅当存在某个数i满足两个句子的第i个单词不同。注意,原句子也应被纳入统计,因为显然小黑可以复述出相同的句子。答案可能很大,请输出答案对1000000007取模的结果。
【输入格式】
从文件 string.in 中读入数据。
第一行一个整数n,表示句子中包含n个单词。
接下来一行n个字符串,第i个字符串表示第i个单词。
【输出格式】
输出到文件 string.out 中。
一行,一个整数,表示答案对1000000007取模的结果。
【样例 1 输入】
3
it is me
【样例 1 输出】
3
【样例 1 解释】
小黑复述的3种句子可以是:
it is me
is it me
it me is
【样例 2 输入】
13
yi yi si wu yi si yi jiu yi jiu ba yao ling
【样例 2 输出】
233
【样例 3 】
见下发文件中的string3.in和string3.ans。
【数据范围】
对于前20% 的数据,n ≤ 20。
对于前50% 的数据,n ≤ 5000。
对于另10% 的数据,所有单词长度均为1。
对于100% 的数据,n ≤ 100000,所有单词总长不超过2000000。
「大力DP即可,送分题,然而我hash不知道为啥正着hash会挂一个点...」
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 100000 + 10;
inline int read() {
int s = 0, w = 1;
char c = getchar();
while (c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
while (c >= '0' && c <= '9') s = s * 10 + c - '0', c = getchar();
return s * w;
}
const int base = 233;
const int p1 = 998244353;
const int p2 = 19260817;
const int p = 1000000007;
long long ha[maxn], sh[maxn];
long long dp[maxn];
int n;
char s[2000002];
int main() {
freopen("string.in", "r", stdin);
freopen("string.out", "w", stdout);
n = read();
for (int i = 1; i <= n; i++) {
scanf("%s", s + 1);
int len = strlen(s + 1);
for (int j = len; j >= 1; j--) {
ha[i] = (ha[i] * base + s[j] - 'a') % p1;
sh[i] = (sh[i] * base + s[j] - 'a') % p2;
}
}
dp[0] = dp[1] = 1;
for (int i = 2; i <= n; i++) {
if (ha[i] != ha[i - 1] || sh[i] != sh[i - 1])
dp[i] = (dp[i - 1] + dp[i - 2]) % p;
else
dp[i] = dp[i - 1];
}
printf("%lld\n", dp[n]);
return 0;
}
全球覆盖( globe )
【题目描述】
小黑正在研发一款全球定位软件,想用它来定位小白的坐标。具体来说,地球可以看做一个\(X \times Y\)的网格矩阵,横纵坐标范围分别是\([0, X)\)和\([0, Y)\),由于地球是球形结构,网格的上边界和下边界是相通的,左边界和右边界也是相通的。
现在小黑获得了\(n\)组坐标对,每组坐标对含有两个点的坐标\((x_{i0} , y_{i0})\), \((x_{i1} , y_{i1})\),表示地球上一个两边平行于坐标轴的矩形的两个对角顶点,而小白就在这个矩形内部。
然而,由于地球是球形结构,确定了坐标对后仍然有多种可能的“矩形”(如下图所示)。
小黑想知道最多可能有多少面积的网格出现在所有“矩形”的交集之中,以方便他确定小白的位置。每个单元格的面积为1。
于是他把这个问题交给你了。
【输入格式】
从文件 globe.in 中读入数据。
第一行三个正整数n, X, Y,含义如题目描述。
接下来n行,每行四个正整数x i,0 , y i,0 , x i,1 , y i,1 ,描述一组坐标对。所有数据始终保
证有x i,0 < x i,1 , y i,0 < y i,1 。
【输出格式】
输出到文件 globe.out 中。
一行,一个整数,表示所求的答案。
【样例 1 输入】
2 10 7
2 1 8 6
4 2 5 4
【样例 1 输出】
15
【样例 1 解释】
样例中的情况和题目中图片一致,其中第三种情况的面积最大。
【样例 2 】
见下发文件中的globe2.in和globe2.ans。
【数据范围】
对于前10% 的数据,n ≤ 10。
对于前20% 的数据,n ≤ 20。
对于另50% 的数据,n ≤ 3000。
对于100% 的数据,n ≤ 500000,2 ≤ X, Y ≤ \(10^9\) , 0 ≤ \(x_0\) , \(x_1\) < X, 0 ≤ \(y_0\) , \(y_1\) < Y。
「后两道题都是神仙题...跟前面两个不是一个级别的...」
「想办法证明每个矩形的四种贡献都可以通过挪动转移成四整块完整的矩形」
「再想办法证明横纵坐标是互不相关的,这样答案就变为横坐标中最长的答案×纵坐标中最长的答案」
「分别处理x轴和y轴,仍然先将横纵坐标分开做。对坐标进行离散化,发现每一段被关键点夹在中间的线段,想要被选中就一定存在唯一确定的矩形覆盖方案(可以用01状态来表示每个矩形的覆盖情况),状态可以用哈希加差分维护。对于每段线段,将其他线段扫一遍,求出和自己的需求一致的线段长度和,再取最大值,这个查询用哈希表来实现。」
「安利一波犇犇wyf,我的哈希表是从这里学的(虽然是zxk的码233)」
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ull unsigned long long
#define ll long long
const int maxn = 5e5 + 10;
const int mod1 = 1e9 + 7;
const int mod2 = 998244353;
const int p = 19260817;
const int base = 233;
char buf[1 << 20], *p1 = buf, *p2 = buf;
char getc() {
if (p1 == p2) {
p1 = buf, p2 = buf + fread(buf, 1, 1 << 20, stdin);
if (p1 == p2) return EOF;
}
return *p1++;
}
inline int read() {
int s = 0, w = 1;
char c = getc();
while (c < '0' || c > '9') { if (c == '-') w = -1; c = getc(); }
while (c >= '0' && c <= '9') s = s * 10 + c - '0', c = getc();
return s * w;
}
int n, ans, maxx, maxy;
struct node {
int pos, w, id;
node() {}
node(int a, int b, int c) {
pos = a, w = b, id = c;
}
}x[maxn << 1], y[maxn << 1];
bool cmp(node a, node b) {
return a.pos < b.pos;
}
struct Hash {
int nex, len;
ll data1, data2;
}hash[maxn << 1];
ull po[maxn], ow[maxn];
int head[20000000], tot;
void Add(int u, int len, ull data1, ull data2) {
hash[++tot].nex = head[u];
hash[tot].len = len;
hash[tot].data1 = data1;
hash[tot].data2 = data2;
head[u] = tot;
}
void Insert(ll a, ll b, int len) {
int u = a % p;
for (int i = head[u]; i; i = hash[i].nex) {
if (hash[i].data1 == a && hash[i].data2 == b) {
hash[i].len += len;
ans = std:: max(ans, hash[i].len);
return;
}
}
Add(u, len, a, b);
ans = std:: max(ans, len);
}
int Solve(node a[], int mx) {
memset (head, 0, sizeof head);
ans = tot = 0;
ull ha = 0, sh = 0;
std:: sort (a + 1, a + 1 + n * 2, cmp);
for (int i = 1; i < n * 2; i++) {
ha = (ha + po[a[i].id] * a[i].w) % mod1;
sh = (sh + ow[a[i].id] * a[i].w) % mod2;
Insert(ha, sh, a[i + 1].pos - a[i].pos);
}
Insert(0, 0, a[1].pos);
Insert(0, 0, mx - a[n + n].pos);
return ans;
}
int main() {
freopen("globe.in", "r", stdin);
freopen("globe.out", "w", stdout);
n = read(), maxx = read(), maxy = read();
po[0] = 1, ow[0] = 1;
for (int i = 1; i <= n * 2; i++) po[i] = (po[i - 1] * base) % mod1, ow[i] = (ow[i - 1] * base) % mod2;
for (int i = 1; i <= n; i++) {
x[i] = node(read(), 1, i);
y[i] = node(read(), 1, i);
x[i + n] = node(read(), -1, i);
y[i + n] = node(read(), -1, i);
}
printf("%lld\n", 1LL * Solve(x, maxx) * Solve(y, maxy));
return 0;
}
幂次序列( sequence )
【题目描述】
小黑和小白又在玩游戏。小黑有一个序列,每个元素都形如2 x ,其中x是整数。小白每次可以选择序列里连续的一段,然后计算这段区间内所有元素的总和,记为s,也就是将这段区间合并为一个数。为了让游戏更有难度,小黑要求小白合并时必须保证s也是2 x 形式的数。
然而,小白不擅长计算,因此她很难找到一个合法的区间。于是她向你求助,想知道对于给定的初始序列,有多少区间可以保证合并后产生的s也是2 x 形式的数。
注意,如果一个区间只有1个数,也被视为是合法的区间。
【输入格式】
从文件 sequence.in 中读入数据。
第一行一个正整数n,表示序列的长度。
接下来一行n个整数a i ,表示序列的第i个元素为2 a i 。
【输出格式】
输出到文件 sequence.out 中。
一行,一个整数,表示所求的答案。
【样例 1 输入】
3
1 1 2
【样例 1 输出】
5
【样例 1 解释】
一共有5个合法区间,[1,1], [2,2], [3,3], [1,2], [1,3]。
【样例 2 】
见下发文件中的sequence2.in和sequence2.ans。
【样例 3 】
见下发文件中的sequence3.in和sequence3.ans。
【数据范围】
对于前10% 的数据,n ≤ 100。
对于前20% 的数据,n ≤ 1000。对于前50% 的数据,n ≤ 5000。
对于前80% 的数据,n ≤ 50000。
前80% 的数据中,有4个测试点满足\(a_i\) ≤ 2,还有另外4个测试点满足\(a_i\) ≤ 30。
对于100% 的数据,n ≤ 200000,1 ≤ \(a_i\) ≤ \(10^9\) 。
「思路大概能明白,CDQ分治。分别考虑最大值在左边和在右边的情况,用哈希表维护,细节巨多,这个蒟蒻并不会写...所以借(chao)鉴(xi)了别人的代码,这题好像全机房的码都长得差不多...」
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ull unsigned long long
const int maxn = 2e5 + 10;
const int mod1 = 1e9 + 7;
const int mod2 = 998244353;
const int p = 19260817;
const int base = 233;
char buf[1 << 20], *p1 = buf, *p2 = buf;
char getc() {
if (p1 == p2) {
p1 = buf, p2 = buf + fread(buf, 1, 1 << 20, stdin);
if (p1 == p2) return EOF;
}
return *p1++;
}
inline int read() {
int s = 0, w = 1;
char c = getc();
while (c < '0' || c > '9') { if (c == '-') w = -1; c = getc(); }
while (c >= '0' && c <= '9') s = s * 10 + c - '0', c = getc();
return s * w;
}
struct Hash {
int nex;
long long data1, data2;
Hash() {}
Hash(int a, long long b, long long c) {
nex = a, data1 = b, data2 = c;
}
}e[maxn << 1];
int n, head[p + 10], tot;
int a[maxn], pos[maxn], mx[maxn], po1[maxn][21], po2[maxn][21];
long long suml[maxn], sumr[maxn];
int ans;
void Add(long long data1, long long data2) {
int u = data1 % p;
e[++tot].nex = head[u];
e[tot].data1 = data1;
e[tot].data2 = data2;
head[u] = tot;
}
bool Check(long long x, long long y) {
int u = x % p;
for (int i = head[u]; i; i = e[i].nex) {
if (e[i].data1 == x && e[i].data2 == y) return 1;
}
return 0;
}
void Init(int i) {
//left
mx[i] = a[i];
pos[i] = i;
//right
mx[i + 1] = a[i + 1];
pos[i + 1] = i + 1;
suml[i] = po1[i][0];
suml[i + 1] = po1[i + 1][0];
sumr[i] = po2[i][0];
sumr[i + 1] = po2[i + 1][0];
}
void CDQ(int l, int r) {
if (l == r) return ans++, void();
int mid = (l + r) >> 1;
CDQ(l, mid);
CDQ(mid + 1, r);
Init(mid);
for (int i = mid - 1; i >= l; i--) {
if (a[i] >= mx[i + 1]) mx[i] = a[i], pos[i] = i;
else mx[i] = mx[i + 1], pos[i] = pos[i + 1];
suml[i] = (suml[i + 1] + po1[i][0]) % mod1;
sumr[i] = (sumr[i + 1] + po2[i][0]) % mod2;
}
for (int i = mid + 2; i <= r; i++) {
if (a[i] >= mx[i - 1]) mx[i] = a[i], pos[i] = i;
else mx[i] = mx[i - 1], pos[i] = pos[i - 1];
suml[i] = (suml[i - 1] + po1[i][0]) % mod1;
sumr[i] = (sumr[i - 1] + po2[i][0]) % mod2;
}
int j = mid;
for (int i = mid + 1; i <= r; i++) {
while (j >= l && mx[j] < mx[i]) {
Add(suml[j], sumr[j]);
j--;
}
for (int k = 0; k <= 20; k++) {
ans += Check((po1[pos[i]][k] - suml[i] + mod1) % mod1, (po2[pos[i]][k] - sumr[i] + mod2) % mod2);
}
}
for (int i = j + 1; i <= mid; i++) head[suml[i] % p] = 0;
tot = 0;
j = mid + 1;
for (int i = mid; i >= l; i--) {
while (j <= r && a[j] <= a[i]) {
Add(suml[j], sumr[j]);
j++;
}
for (int k = 0; k <= 20; k++) {
ans += Check((po1[pos[i]][k] - suml[i] + mod1) % mod1, (po2[pos[i]][k] - sumr[i] + mod2) % mod2);
}
}
for (int i = j - 1; i >= mid + 1; i--) head[suml[i] % p] = 0;
tot = 0;
}
long long Qpow(int t, int mod) {
long long ans = 1, s = 2;
while (t) {
if (t & 1) ans = ans * s % mod;
t >>= 1;
s = s * s % mod;
}
return ans;
}
int main() {
freopen("sequence.in", "r", stdin);
freopen("sequence.out", "w", stdout);
n = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
po1[i][0] = Qpow(a[i], mod1), po2[i][0] = Qpow(a[i], mod2);
for (int j = 1; j <= 20; j++) {
po1[i][j] = (po1[i][j - 1] * 2) % mod1;
po2[i][j] = (po2[i][j - 1] * 2) % mod2;
}
}
CDQ(1, n);
printf("%d\n", ans);
return 0;
}