1123考试总结
1123考试总结
T1
题目大意:
David 有很多好朋友。有些期末季刚结束,有些人很快乐,但有些不太快乐,David 想把快乐传递给每个人,作为心理学大师,他准备了如下计划:David 的朋友中有 n 个男生和 m 个女生, 还有 k 个跨性别者,方便起见,将他们分别编号为 0,...,n−1 和 0,...,m−1, 0,...,k −1,在第 i 天,David会邀请编号为 (i mod n) 的男生和编号为 (i mod m) 的女生还有 (i mod k)的跨性别者共进晚餐(因为 David 同时是程序员,所以从这个计划从第 0天开始)。共进晚餐的三个人只要至少有有一个是快乐的人,另外的人也会变得快乐起来。否则大家的状态不会改变(一旦一个人是快乐的,他就会永远快乐下去)。
现在问题来了,David 想知道他是否能通过这个计划使得所有人都快乐起来呢?
对于 100% 的数据,n,m,k ≤ 1000000000, b,g,t ≤ 100000, b, g, t表示目前快乐的男, 女, 跨性别者的人数, 后边有具体编号.
数学.
假设当前只有男生和女生, 哪些人会在一起吃饭呢? \(x \equiv y \pmod {gcd(n,m)}\).
证明方法 : 找规律
假设在\(i\)轮时男生\(x\)会和女生\(y\)在一起吃饭, 那么我们可以得到 : \(i = x + pn = y + qm(p, q \in Z)\).
设\(g = gcd(n,m)\), 那么\(g \mid n, g \mid m\), 所以\(g \mid pn , g \mid qm\). 那么就可以得到上面的式子啦.
扩展到男生, 女生, 跨性别者也是一样的.\(x \equiv y \equiv z \pmod {gcd(n, m, k)}\) 这些人将会在一起吃饭.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e7 + 5;
int n, m, k, b, g, t, tag, gcd;
int v[N];
int GCD(int x, int y) { return !y ? x : GCD(y, x % y); }
int main() {
for(int T = read(); T ; T --) {
n = read(); m = read(); k = read();
if(k == 0) {
gcd = GCD(n, m);
for(register int i = 0;i < gcd; i++) v[i] = 0;
b = read(); for(int i = 1;i <= b; i++) v[read() % gcd] = 1;
g = read(); for(int i = 1;i <= g; i++) v[read() % gcd] = 1;
t = read(); tag = 0;
for(register int i = 0;i < gcd; i++) if(!v[i]) { tag = 1; break; }
if(tag) printf("No\n"); else printf("Yes\n");
}
else {
gcd = GCD(GCD(n, m), k);
for(register int i = 0;i < gcd; i++) v[i] = 0;
b = read(); for(int i = 1;i <= b; i++) v[read() % gcd] = 1;
g = read(); for(int i = 1;i <= g; i++) v[read() % gcd] = 1;
t = read(); for(int i = 1;i <= t; i++) v[read() % gcd] = 1; tag = 0;
for(register int i = 0;i < gcd; i++) if(!v[i]) { tag = 1; break; }
if(tag) printf("No\n"); else printf("Yes\n");
}
}
fclose(stdin); fclose(stdout);
return 0;
}
T2
题目大意:
为了找出嫌疑人,David 开了一个会。会上有 n 个程序员,每个程序员都发表了如下言论:“不是 x 干的就是 y 干的!”(不同程序员言语中的 x 与 y不一定相同)David 决定选出两个嫌疑人,并请他们去他的办公室喝茶。当然 David 的选取方案会参考所有程序员们的建议。他想让他的选取方案至少有 p 个人支持。一个程序员会支持一个方案当且仅当至少有一个该程序员认定的嫌疑人被请去喝茶了。
注意:假如某个程序员被选为两个嫌疑人之一,他甚至可能支持这个选取方案,只要选取的另一个人是他说的两个嫌疑人之一即可.
\(n <= 300000, p <= n\)
乱搞 + 树状数组.
类似于建图, 我们将一个言论里的\(x, y\)连边, 然后记录一下每个点的度数d, 那么我们要找的合法方案应该是\(d_i +d_j >= p\).
然后我们枚举每一个\(i\), 分情况讨论:
如果说\(d_i >= p\), 那么\(ans += n - 1\), 这个人与任何人都可以组成合法方案.
如果说\(d_i < p\), 那么我们要取找合法的\(j\)有多少个, 首先要找到\(d_j >= p - d_i\)的那些人, 但是还没有完, 如果说\(i, j\)有直接连边, 那么上面那个式子是不是有点问题, 应该改成\(d_i +d_j - 1>= p\), 因为你有一条边重复统计了.所以我们还应该在减去\(d_j == p - d_i\)的那些人, 这样才能得到正确的答案.
但是对于这个\(j\)我们是不能直接枚举的, 复杂度不允许.我们考虑用树状数组维护一个前缀和, 这样就可以把时间复杂度优化到\(O(n log n)\)了.具体看代码吧.
#include <iostream>
#include <cstdio>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 3e5 + 5;
int n, p, cnt, d[N], head[N];
long long ans, sum, t[N];
struct edge { int to, nxt; } e[N << 1];
void add(int x, int y) {
e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y;
}
int lowbit(int x) { return x & -x; }
void change(int x) {
if(x == 0) t[x] ++;
else for( ; x < N ; x += lowbit(x)) t[x] ++;
}
long long query(int x) {
long long res = 0; for( ; x ; x -= lowbit(x)) res += t[x]; return res + t[0];
}
int main() {
n = read(); p = read();
for(int i = 1, x, y;i <= n; i++) x = read(), y = read(), d[x] ++, d[y] ++, add(x, y), add(y, x);
for(int i = 1;i <= n; i++) change(d[i]);
for(int x = 1;x <= n; x++) {
if(d[x] >= p) ans += n - 1;
else {
sum = n - query(p - d[x] - 1); //找出度数大于等于p - d[x]的人数
if(d[x] >= p - d[x]) sum --; //自己不能算
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to;
if(d[x] + d[y] == p) sum --; //减去两个点有直接连边的那些点
d[y] --;
}
for(int i = head[x]; i ; i = e[i].nxt) d[e[i].to] ++;
ans += sum;
}
}
printf("%lld", ans / 2);
return 0;
}
T3
题目链接
线段树.
这道题让求出现偶数次的数字的异或和, emmmm有点难搞, 如果说是求出现奇数次的就好了.
那我们可不可以把转变一下, 给这段区间每个数字出现的次数减一, 然后奇数变偶数, 偶数变奇数, 这样答案就是减完之后的异或和了.
我们考虑用线段树维护这样一个东西 : 区间\([l, r]\)出现过的数字的异或和, 比如这一段区间内的数是"1, 1, 2, 3, 4, 4", 那么线段树就维护了1 ^ 2 ^ 3 ^ 4.
为啥要维护这个呢? 把这个东西与原来所有数的异或和再异或一下, 就发现得到答案了, 因为相当于每个数多异或了一次, 这与次数减一是一样的.还是上面那个例子 : (1 ^ 1 ^ 2 ^ 3 ^ 4 ^ 4) ^ (1 ^ 2 ^ 3 ^ 4) = (1 ^ 4).
#include <bits/stdc++.h>
#define ls(o) (o << 1)
#define rs(o) (o << 1 | 1)
#define mid ((l + r) >> 1)
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e6 + 5;
int n, m, cnt, tim;
long long t[N << 2], ans[N], sum[N];
int b[N], a[N], nxt[N], last[N];
struct sect { int l, r, id; } q[N];
int cmp(sect a, sect b) { return a.l < b.l; }
void change(int o, int l, int r, int x, int val) {
if(l == r) { t[o] = val; return ; }
if(x <= mid) change(ls(o), l, mid, x, val);
if(x > mid) change(rs(o), mid + 1, r, x, val);
t[o] = t[ls(o)] ^ t[rs(o)];
}
long long query(int o, int l, int r, int x, int y) {
if(x <= l && y >= r) return t[o];
long long res = 0;
if(x <= mid) res = res ^ query(ls(o), l, mid, x, y);
if(y > mid) res = res ^ query(rs(o), mid + 1, r, x, y);
return res;
}
int main() {
n = read();
for(register int i = 1;i <= n; i++) b[i] = a[i] = read(), sum[i] = sum[i - 1] ^ a[i];
sort(b + 1, b + n + 1);
cnt = unique(b + 1, b + n + 1) - b - 1;
for(register int i = 1;i <= n; i++) a[i] = lower_bound(b + 1, b + cnt + 1, a[i]) - b;
for(register int i = n;i >= 1; i--) nxt[i] = last[a[i]], last[a[i]] = i;
for(register int i = 1;i <= cnt; i++) change(1, 1, n, last[i], b[i]);
m = read();
for(register int i = 1;i <= m; i++) q[i].l = read(), q[i].r = read(), q[i].id = i;
sort(q + 1, q + m + 1, cmp); int l = 1;
for(register int i = 1;i <= m; i++) {
while(l < q[i].l) change(1, 1, n, nxt[l], b[a[l]]), l ++;
ans[q[i].id] = sum[q[i].r] ^ sum[q[i].l - 1] ^ query(1, 1, n, q[i].l, q[i].r);
}
for(register int i = 1;i <= m; i++) printf("%lld\n", ans[i]);
return 0;
}
T4
题目大意 :
有\(n\)个物品, 每个物品的体积为\(V_1,V_2,...V_n\), 每种物品都有无限多个.现有\(m\)次询问, 每次询问一个容量为\(W\)的背包是否可以恰好被装满, 并且对于体积大于等于\(L\)的物品最多选\(C\)个, 如果可以被装满, 输出\(Yes\), 否则输出\(No\).
\(n <= 50, V <= 10000, W <= 10 ^ {18}, C <= 30, L <= 20000\)
DP + 最短路.
假设只有\(V < L\)的那些物品, 我们可以发现, 如果\(x \equiv W \bmod V_1\), 并且\(x\)可以被凑出来, 那么\(W\)也一定可以被凑出来.
所以可以得到DP思路, 对于\(V >= L\)的那些物品要记录个数, 并且要记录一下\(V_1\)的剩余系.
\(f[i][j]\)表示体积模\(V_1\)后得到\(i\), 并且用了\(j\)个体积大于等于\(L\)的物品所需要的最小体积. 那么转移就是完全背包.这是对于\(V >= L\)的那些物品来说的.
对于\(V < L\)的那些物品的转移, 就是\(f[(i + V) \% V_1][j] = f[i][j] + V\), 我们会发现这个转移是有环的, 所以我们可以建边来跑SPFA.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e4 + 5;
const long long inf = 1e18;
int n, m, l, c, cnt;
int v[N], in[N], head[N];
long long dis[N], f[N][50];
struct edge { int to, nxt, val; } e[N * 50];
void add(int x, int y, int z) {
e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y; e[cnt].val = z;
}
void SPFA() {
for(int j = 0;j < v[1]; j++)
for(int i = 1;i <= n; i++) {
if(v[i] >= l) continue;
add(j, (j + v[i]) % v[1], v[i]);
}
for(int i = 0;i < v[1]; i++) dis[i] = inf;
for(int i = 0;i < v[1]; i++)
for(int j = 0;j <= c; j++) dis[i] = min(dis[i], f[i][j]);
queue <int> q;
for(int i = 0;i < v[1]; i++) if(dis[i] != inf) q.push(i), in[i] = 1;
while(!q.empty()) {
int x = q.front(); q.pop(); in[x] = 0;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to;
if(dis[y] > dis[x] + e[i].val) {
dis[y] = dis[x] + e[i].val; if(!in[y]) q.push(y);
}
}
}
}
int main() {
n = read(); m = read();
for(int i = 1;i <= n; i++) v[i] = read();
sort(v + 1, v + n + 1);
l = read(); c = read();
for(int i = 0;i < v[1]; i++) for(int j = 0;j <= c; j++) f[i][j] = inf;
f[0][0] = 0;
for(int i = 1;i <= n; i++) {
if(v[i] < l) continue;
for(int j = 1;j <= c; j++)
for(int k = 0;k < v[1]; k++) {
int tmp = ((k - v[i]) % v[1] + v[1]) % v[1];
f[k][j] = min(f[k][j], f[tmp][j - 1] + v[i]);
}
}
SPFA();
for(int i = 1;i <= m; i++) {
long long w = read();
if(dis[w % v[1]] == inf) printf("No\n");
else if(dis[w % v[1]] <= w) printf("Yes\n"); else printf("No\n");
}
return 0;
}