CSP 日照集训考试 Day3
考的还不错,但是没干过内裤。
T1
好一眼啊这个题。
化个式子就做出来了。
然后要求的是若干个正整数解,所以 \(x,y > 0\) 。
因为 \(a,b,c,d\) 都是正整数。
所以 \(acy\) 一定是一个正数,那想要让 \(x>0\) ,那么 \(dc-by>0\) 。
则 \(y<\frac{cd}{b}\) 。
然后就枚举 \(y\) 求 \(x\) 即可。
然后这个 \(cd <= 1e6\) ,所以复杂度很正确。
# include <bits/stdc++.h>
using namespace std;
#define int long long
inline int read () {
int x = 0, f = 1;
char c = getchar ();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar ();}
while (c >= '0' && c <= '9') {x = x * 10 + c - '0', c = getchar ();}
return x * f;
}
int a, b, c, d;
signed main () {
int t; cin >> t;
while (t --) {
a = read (), b = read (), c = read (), d = read ();
int ans = 0;
for (int y = 1; y <= c*d/b + 10; ++ y) {
if (c*d-b*y <= 0) break;
int x = a*c*y/(c*d-b*y);
if (x*(c*d-b*y) != a*c*y) continue;
if (x>0&&y>0) ans++;
}
cout << ans << "\n";
}
}
T2
想了老长时间了。
貌似是先打完了 \(T3\) ,再想出这个题正解的。
我一开始以为是个线段树的题。
异或这一类题有几个很重要的东西,一个是交换律,一个就是前缀和。
那这个题就是考的前缀和。
我一开始想的思路:
sum[]
表示的是前缀异或和。
我先考虑修改某个位置比如说 \(p\) 吧,那 \(sum[p]\) 一直到 \(sum[n]\) 都会被影响到。
那我想呢,用一个线段树维护一下这个前缀异或和。
然后修改的时候从这个地方到结尾整体改一下。
接着就没思路了…想了好几种办法,都不可行,不可行指的是复杂度貌似不对。
然后去想 \(T3\) 了。
写完 \(T3\) ,回来写 \(T2\) ,我觉得实在不行了就打个暴力吧。
然后打着打着……
发现。
貌似进行一次操作对前缀和的影响只是对 \(p\) 这个位置产生影响诶。
然后切了。
具体点的思路:
因为要让某个区间异或和等于零,即 \(sum[r]\) ^ \(sum[l - 1] = 0\) 。
所以只要两个位置的前缀数组的值一样就 ok 。
我们先不考虑别的,先想这个原数组的答案怎么算。
我一开始想了不少方法,有组合数之类的。但是貌似不太行,因为要预处理阶乘,而且这个预处理到什么程度也不知道。
所以,我们考虑一个数对其他数的贡献。一个区间需要两个位置就能确定,所以一个位置对其他位置的贡献就是跟这个位置 \(num\) 值相等的位置的个数,然后会发现,会重复计算贡献,所以出来答案的时候除以 \(2\) 。
考虑删掉和加上某个数产生的贡献。
想要删掉这个位置的数,那他与其他位置构成的区间的个数就要减掉;因为一开始记录答案的时候是算的重复的,所以其他位置与这个位置构成的区间个数也要减掉。
想要把这个位置异或上一个数,也就是添一个新的数,那他对答案的贡献就是把他的值改变后会有多少个跟他相等的数,然后其他的位置对这个位置的贡献也要重复加一下。
没啥了。代码:
# include <bits/stdc++.h>
using namespace std;
const int D = 1e6 + 100;
#define int long long
inline int read () {
int x = 0, f = 1;
char c = getchar ();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar ();}
while (c >= '0' && c <= '9') {x = x * 10 + c - '0', c = getchar ();}
return x * f;
}
int n, m;
int sum[D];
map <int, int> tim;
signed main () {
n = read (), m = read ();
int ans = 0;
tim[0] ++;
sum[0] = 0;
for (int i = 1; i <= n; ++ i) {
sum[i] = read ();
sum[i] = sum[i - 1] ^ sum[i];
tim[sum[i]] ++;
}
for (int i = 0; i <= n; ++ i) ans += tim[sum[i]] - 1;
int p, x;
for (int i = 1; i <= m; ++ i) {
p = read (), x = read ();
ans -= 2 * (tim[sum[p]] - 1);
tim[sum[p]] --;
sum[p] ^= x;
tim[sum[p]] ++;
ans += 2 * (tim[sum[p]] - 1);
cout << ans / 2 << "\n";
}
}
T3
一眼线段树题。
题目中给定几个字符串,我们可以把他们看成若干条件。
例如 \(10??101\) 和 \(1?0??01\) 。
需要让我们找出若干个字符串与这两个字符串匹配。
很容易就能发现,想要同时满足这两个条件,那就需要把这两个条件合并。
只要满足合并后的条件,那就一定满足这两个条件中任意的一个。
所以关键在于怎么合并条件。
可以按照以下的方式:
每一行分别表示:前一个字符串上的某一位;第二个字符串上对应的位置;合并后变成什么
现在就知道怎么合并了。
因为这个算法涉及到合并,那很容易就能够想到利用一下线段树。
因为每个字符串最多的话是 \(30\) 位。
这个时候可以有两种做法:
- 开 \(30\) 棵线段树,大概是维护字符串每一位?
- 线段树每个叶子维护的是一个字符串,两个两个往上合并,上边的节点就是合并后的字符串。感觉第二种应该好理解,因为我用的是第二种。
我们合并的时候可以枚举,然后按照上边的合并方法合并出一个新的字符串。
void pushup (int now) {
bz[now] = 0;
if (bz[now << 1] || bz[now << 1 | 1]) {
bz[now] = 1;
return;
}
string tmp1 = s[now << 1], tmp2 = s[now << 1 | 1], tmp = "";
for (int i = 0; i < n; ++ i) {
if (tmp1[i] == '?') {
if (tmp2[i] != '?') tmp += tmp2[i];
else tmp += '?';
}
else if (tmp2[i] == '?') {
if (tmp1[i] != '?') tmp += tmp1[i];
else tmp += '?';
}
else if (tmp1[i] == tmp2[i]) tmp += tmp1[i];
else {
bz[now] = 1;
return;
}
}
s[now] = tmp;
}
我的合并方法是这样的,仅供参考。
bz[]
表示当前节点是不是有必要继续往上合并,因为如果出现 \(0,1\) 配对这种的就说明不存在一个可行的字符串了。
s[]
表示当前节点维护的区间的字符串。
然后最重要的东西就只有这个合并操作了,其他的没啥了。
代码:
# include <bits/stdc++.h>
using namespace std;
const int D = 1e5 + 10;
inline int read () {
int x = 0, f = 1;
char c = getchar ();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar ();}
while (c >= '0' && c <= '9') {x = x * 10 + c - '0', c = getchar ();}
return x * f;
}
int n, m, q;
string s[D << 2], x[D];
bool bz[D << 2];
void pushup (int now) {
bz[now] = 0;
if (bz[now << 1] || bz[now << 1 | 1]) {
bz[now] = 1;
return;
}
string tmp1 = s[now << 1], tmp2 = s[now << 1 | 1], tmp = "";
for (int i = 0; i < n; ++ i) {
if (tmp1[i] == '?') {
if (tmp2[i] != '?') tmp += tmp2[i];
else tmp += '?';
}
else if (tmp2[i] == '?') {
if (tmp1[i] != '?') tmp += tmp1[i];
else tmp += '?';
}
else if (tmp1[i] == tmp2[i]) tmp += tmp1[i];
else {
bz[now] = 1;
return;
}
}
s[now] = tmp;
}
void build (int now, int l, int r) {
if (l == r) {
s[now] = x[l];
return;
}
int mid = l + r >> 1;
build (now << 1, l, mid);
build (now << 1 | 1, mid + 1, r);
pushup (now);
}
bool flag;
string query (int now, int l, int r, int L, int R) {
if (flag) return "";
if (L <= l && R >= r) {
if (bz[now]) {
flag = 1;
return "";
}
return s[now];
}
int mid = l + r >> 1;
string tmp1, tmp2, tmp = "";
bool bj1 = 0,bj2 = 0;
if (L <= mid) bj1=1,tmp1 = query (now << 1, l, mid, L, R);
if (R >= mid + 1) bj2=1,tmp2 = query (now << 1 | 1, mid + 1, r, L, R);
if (flag) return "";
if (!bj1) return tmp2;
if (!bj2) return tmp1;
for (int i = 0; i < n; ++ i) {
if (tmp1[i] == '?') {
if (tmp2[i] != '?') tmp += tmp2[i];
else tmp += '?';
}
else if (tmp2[i] == '?') {
if (tmp1[i] != '?') tmp += tmp1[i];
else tmp += '?';
}
else if (tmp1[i] == tmp2[i]) tmp += tmp1[i];
else {
flag = 1;
return "";
}
}
return tmp;
}
void update (int now, int l, int r, int x, string c) {
if (l == r) {
s[now] = "";
s[now] += c;
return;
}
int mid = l + r >> 1;
if (x <= mid) update (now << 1, l, mid, x, c);
else update (now << 1 | 1, mid + 1, r, x, c);
bz[now] = 0;
pushup (now);
}
int main () {
cin >> n >> m >> q;
for (int i = 1; i <= m; ++ i) cin >> x[i];
build (1, 1, m);
int ans = 0;
int opt, l, r, p;
string tmp;
for (int i = 1; i <= q; ++ i) {
opt = read ();
if (opt == 0) {
l = read (), r = read ();
flag = 0;
string tmp = query (1, 1, m, l, r);
if (flag) continue;
int num = 0;
for (int j = 0; j < n; ++ j) num += tmp[j] == '?';
ans ^= (1 << num);
}
else {
tmp = "";
p = read ();
cin >> tmp;
update (1, 1, m, p, tmp);
}
}
cout << ans;
}
建议用 \(char\) ,要不比暴力还低。
润了,我不想改了。