比赛链接:
https://ac.nowcoder.com/acm/contest/11216
A.憧憬
题目大意:
给了 \(n\) 个向量及一个目标向量的起点和终点坐标,判断有没有两个向量相加得到的向量与目标向量平行。
思路:
暴力枚举任意两个向量的组合,然后通过向量平行的性质 \(x_1 * y_2 - x_2 * y_1 = 0\) 去判断一下就行。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e3 + 10;
LL n, x[N], y[N], a, b, t1, t2, t3, t4;
int main(){
cin >> n;
for (int i = 1; i <= n; ++ i){
cin >> t1 >> t2 >> t3 >> t4;
x[i] = t3 - t1;
y[i] = t4 - t2;
}
cin >> t1 >> t2 >> t3 >> t4;
a = t3 - t1;
b = t4 - t2;
for (int i = 1; i <= n; ++ i){
for (int j = i + 1; j <= n; ++ j){
LL p = x[i] + x[j], q = y[i] + y[j];
if (p * b - q * a == 0){
cout << "YES\n";
return 0;
}
}
}
cout << "NO\n";
return 0;
}
B.欢欣
题目大意:
在给定字符串中找到 "QAQ" 这个字符串。
思路:
调用 find() 函数就行。
代码:
#include <bits/stdc++.h>
using namespace std;
string s, t = "QAQ";
int main(){
cin >> s;
cout << s.find(t) + 1 << "\n";
return 0;
}
C.奋发
题目大意:
给定两个非降的两个长度为 \(n\) 的序列 \(a\),\(b\)。有两个变量 \(A\),\(B\),初始值为 0。接着进行下列操作。
1.如果 \(A = a_n\) 并且 \(B = b_n\) 时,结束操作。
2.否则,如果 \(\exists t\),使得 \(a_t = A\) 且 \(b_t > B\),将 \(B\) 加 1。
3.否则,如果 \(\exists t\),使得 \(a_t = B\) 且 \(a_t > A\),将 \(A\) 加 1。
4.否则,任选一个加 1。
每次操作后,若存在 \(A = B\),则 \(ans\) 加 1。
求 \(ans\) 最大是多少。
思路:
记之前的 \(a\) 和 \(b\) 为 \(p\) 和 \(q\)。
可能出现四种情况。
1.a == p && b == q
2.a > p && b == q
3.a == p && b > q
4.a > p && b > q
将四种情况讨论一下就行。
也可以归纳一下,其实每次出现相等的次数就是 \(max(0LL, min(a, b) - max(p, q) + (p != q))\)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define IOS() ios::sync_with_stdio(false);cin.tie(0);
#define LL long long
LL n, ans, a, b, p, q;
int main() {
IOS();
cin >> n;
for (int i = 1; i <= n; ++ i){
cin >> a >> b;
if (a == p && b == q) continue;
else if (a > p && b == q){
if (p < b && a >= b) ans++;
}
else if (a == p && b > q){
if (q < a && b >= a) ans++;
}
else if (a > p && b > q){
if (p > b || q > a);
else if (p != q) ans += min(a, b) - max(p, q) + 1;
else ans += min(a, b) - max(p, q);
}
p = a, q = b;
}
cout << ans << endl;
return 0;
}
D.绝望
题目大意:
一个 \(n\) 个元素的序列,第 \(i\) 个为 \(a_i\),有两种操作。
1.输入 1 \(l\) \(r\) \(x\) 四个数,表明将 \(l\) 到 \(r\) 这个区间中所有数都乘上 \(i^x\),并输出 \(l\) 到 \(r\) 这个区间中的质数个数。
2.输入 2 \(l\) \(r\) 三个数,输出 \(l\) 到 \(r\) 这个区间中的质数个数。
思路:
先欧拉筛预处理一下质数,便于后续操作。涉及到了单点的修改和查询,可以想到用树状数组或者线段树。
接下来讨论一下情况。
当 \(x\) = 0 的时候,不论怎么操作都改变不了质数的数量。
当 \(x\) = 1 的时候,如果当前位置上是 1,且下标为质数的时候,这个区间中的质数数量 +1,否则它会将区间中所有的质数都变成合数。
当 \(x\) > 1 的时候,也会将区间中的所有质数变成合数。
遍历操作的区间,然后一个一个判断显然不合理,容易发现,其实有影响只是 1 和质数所在的位置。
用两个 \(set\) 存放 1 和质数的位置,通过 \(lower_bound\) 查询,接下来 \(update\) 树状数组就好了。
如果用线段树的话,每个节点增加两个属性,是否是质数和是否是 1,就可以了。
代码:
树状数组
#include <bits/stdc++.h>
using namespace std;
#define IOS() ios::sync_with_stdio(false);cin.tie(0);
const int N = 5e5 + 10;
int n, m, op, l, r, x, tree[N], cnt, prime[N], v[N];
set <int> p, one;
void init(){
v[1] = 1;
for (int i = 2; i <= 500000; ++ i){
if (!v[i]) prime[++cnt] = i;
for (int j = 1; j <= cnt && i * prime[j] <= 500000; ++ j){
v[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
}
int lowbit(int k){
return k & -k;
}
void update(int x, int k){
while (x <= n){
tree[x] += k;
x += lowbit(x);
}
}
int query(int x){
int t = 0;
while (x != 0){
t += tree[x];
x -= lowbit(x);
}
return t;
}
int main() {
IOS();
init();
p.insert(500010);
one.insert(500010);
cin >> n >> m;
for (int i = 1; i <= n; ++ i){
cin >> x;
if (!v[x]){
p.insert(i);
update(i, 1);
}
else if (x == 1) one.insert(i);
}
for (int i = 1; i <= m; ++ i){
cin >> op >> l >> r;
if (op == 1){
cin >> x;
if (x == 0);
else {
while (1){
int k = *p.lower_bound(l);
if (k > r) break;
if (k != 1) update(k, -1);
p.erase(k);
}
while (1){
int k = *one.lower_bound(l);
if (k > r) break;
if (!v[k] && x == 1){
update(k, 1);
p.insert(k);
}
one.erase(k);
}
}
}
cout << query(r) - query(l - 1) << "\n";
}
return 0;
}
E.迷惘
题目大意:
给了 \(n\) 个数字 \(x\),将它转为二进制后进行翻转,然后去掉前导 0,再将它转回十进制,计算这 \(n\) 个数字进行这个操作后的和。
思路:
按题意模拟就行。
代码:
#include <bits/stdc++.h>
using namespace std;
#define IOS() ios::sync_with_stdio(false);cin.tie(0);
#define LL long long
LL ans, n, x;
int main(){
IOS();
cin >> n;
for (int i = 1; i <= n; ++ i){
cin >> x;
vector <LL> v;
while (x > 0){
v.push_back(x & 1);
x >>= 1;
}
reverse(v.begin(), v.end());
LL p = 1, t = 0;
for (auto x : v){
t += x * p;
p <<= 1;
}
ans += t;
}
cout << ans << "\n";
return 0;
}
F.孤独
题目大意:
在树中找到一条路径,删除所有与这条路径相连的边,求得到的最大连通块的最小值。
思路:
考虑树中每一个节点作为路径的起点,那么删除的路径就是以这个点为起点的两条路径的拼接,接下来就是求以某一个节点为起点的最优路径(删除这条路径后,剩余的最大连通块的数量最小)。
设以 \(i\) 点为起点的最优路径的连通块数量为 \(dp[i]\)。可以知道,最优路径一定在它最大的子树中,所以要知道每一个节点的子树的节点数量,定义为 \(sz[i]\)。
记 \(i\) 点的子树中 \(mx1\) 的数量最大,\(mx2\) 的数量次大。因为删除的路径在最大的子树中,删掉这条路径后子树会有一最大的连通块,而 \(i\) 节点的最大连通块也就是原来的次大连通块,所以答案就是 \(dp[i] = max(dp[mx1], sz[mx2])\)。
最终的路径是由两条路径组合而成,\(i\) 的父亲节点会产生一个连通块,删除的两条路径会产生连通块,与 \(i\) 节点去掉最大的两条路径后,剩下的最大的那个连通块的一个最大值(即原来的第三大的子树),就是删除以 \(i\) 节点为根的路径的最大连通块。
所有节点的答案取一个最小值,就是要求的答案了。
代码:
#include <bits/stdc++.h>
using namespace std;
#define IOS() ios::sync_with_stdio(false);cin.tie(0);
#define pb push_back
const int N = 1e6 + 10;
int n, u, v, sz[N], dp[N], ans = 1e9;
vector <int> g[N];
void dfs(int u, int f){
sz[u] = 1;
int mx1 = 0, mx2 = 0, mx3 = 0;
for (auto v : g[u]){
if (v == f) continue;
dfs(v, u);
sz[u] += sz[v];
if (sz[v] >= sz[mx1]){
mx3 = mx2;
mx2 = mx1;
mx1 = v;
}
else if (sz[v] >= sz[mx2]){
mx3 = mx2;
mx2 = v;
}
else if (sz[v] >= sz[mx3]){
mx3 = v;
}
}
dp[u] = max(dp[mx1], sz[mx2]);
ans = min( ans, max( max( max( dp[mx1], dp[mx2]), n - sz[u] ), sz[mx3]) );
}
int main(){
IOS();
cin >> n;
for (int i = 1; i < n; ++ i){
cin >> u >> v;
g[u].pb(v);
g[v].pb(u);
}
dfs(1, 0);
cout << ans << "\n";
return 0;
}
G.冷静
题目大意:
\(q\) 次询问,每次给定 \(n\) 和 \(k\),问 1 ~ \(n\) 中有多少数可以表示为大于等于 \(k\) 的质数的乘积(一个数可以乘多次)。
思路:
转化一下问题,答案就是求 1 到 \(n\) 中有多少数的最小质因数要大于等于 \(k\),这个可以通过欧拉筛预处理一下。
接下来就是二维偏序的一个问题,答案就是找到某个数 \(x\),它的最小质因数是 \(y\),满足 \(x <= n\) && \(y >= k\)。
先将所有输入按照 \(n\) 的大小排一个序,然后依次将每个数的最小质因数的位置的值 + 1,即单点修改,接着进行区间 \(k\) 到 \(n\) 的查询就行了,即区间查询,通过树状数组或者线段树实现。
代码:
#include <bits/stdc++.h>
using namespace std;
#define IOS() ios::sync_with_stdio(false);cin.tie(0);
#define LL long long
const int N = 3e6 + 10;
int T, v[N], prime[N], mn[N], cnt, ans[N], tree[N];
struct node{
int n, k, id;
}nd[N];
void init(){
v[1] = 1;
for (int i = 2; i <= N - 10; i++){
if (!v[i]) prime[++cnt] = i,mn[i] = i;
for (int j = 1; j <= cnt && i * prime[j] <= N - 10; j++){
v[i * prime[j]] = 1;
mn[i * prime[j]] = prime[j];
if (i % prime[j] == 0) break;
}
}
}
int lowbit(int k){
return k & -k;
}
void update(int x, int k){
while (x <= nd[T].n){
tree[x] += k;
x += lowbit(x);
}
}
int query(int x){
int t = 0;
while (x != 0){
t += tree[x];
x -= lowbit(x);
}
return t;
}
void solve(){
for (int i = 1; i <= T; ++ i){
for (int j = nd[i - 1].n + 1; j <= nd[i].n; ++ j)
update(mn[j], 1);
ans[nd[i].id] = query(nd[i].n) - query(nd[i].k - 1);
}
}
int main(){
IOS();
init();
cin >> T;
for (int i = 1; i <= T; ++ i){
cin >> nd[i].n >> nd[i].k;
nd[i].id = i;
}
nd[0].n = 1;
sort(nd + 1, nd + T + 1, [](node a, node b){
return a.n < b.n;
});
solve();
for (int i = 1; i <= T; ++ i)
cout << ans[i] << "\n";
return 0;
}
H.终别
题目大意:
\(n\) 个怪兽,每个怪兽有 \(a_i\) 点血,可以发出斩击,对连续三个位置上的怪兽造成 1 点伤害,当怪兽血量 <= 0 的时候死亡,现在有一次使用魔法的机会,将连续的两个怪兽直接杀死,计算最少需要几次斩击才能将全部的怪兽杀死。
思路:
首先考虑没有魔法的情况下,需要几次斩击,明显的贪心,从右到左,当某个怪兽存活时,就斩击从这个位置开始的连续三个位置上的怪兽。
现在有了魔法,假设直接杀死第 \(i\) 位和第 \(i + 1\) 上的怪兽,那么问题又变回没有魔法的操作了,将 1 到 \(i - 1\) 和 \(i + 2\) 到 \(n\) 的怪兽斩杀。可以发现这是 O(\(n^2\)) 的操作,超时。
考虑怎么快速计算斩击次数,可以发现第 \(i\) 个位置上的斩击数和前一个位置上的斩击数有关。那么可以通过递推的操作,从前到后以及从后到前,两次预处理斩击数。这就将计算斩击数的操作变为了 O(1) 的时间复杂度。
代码:
#include <bits/stdc++.h>
using namespace std;
#define IOS() ios::sync_with_stdio(false);cin.tie(0);
#define LL long long
const int N = 1e6 + 10;
LL a[N], b[N], pre[N], suf[N], n, ans;
int main(){
IOS();
cin >> n;
for (int i = 1; i <= n; ++ i){
cin >> a[i];
b[i] = a[i];
}
for (int i = 1; i <= n; ++ i){
if (a[i] > 0){
pre[i] = pre[i - 1] + a[i];
a[i + 1] -= a[i];
a[i + 2] -= a[i];
a[i] = 0;
}
else
pre[i] = pre[i - 1];
}
for (int i = n; i >= 1; -- i){
if (b[i] > 0){
suf[i] = suf[i + 1] + b[i];
b[i - 1] -= b[i];
b[i - 2] -= b[i];
b[i] = 0;
}
else
suf[i] = suf[i + 1];
}
ans = pre[0] + suf[3];
for (int i = 2; i <= n - 1; ++ i)
ans = min(ans, pre[i - 1] + suf[i + 2]);
cout << ans << "\n";
return 0;
}