比赛链接:

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;
}
posted on 2022-03-18 20:19  Hamine  阅读(66)  评论(0编辑  收藏  举报