Practice.
A. Polycarp and the Day of Pi
题意
将输入与 "\(314159265358979323846264338327\)" 比对,输出从头开始匹配成功的最大数量。
思路
模拟即可。
时间复杂度:\(O(n)\)
对应AC代码
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int t = scanner.nextInt();
while (t-- > 0) {
String a = "314159265358979323846264338327";
String b = scanner.next();
int n = b.length(), cnt = 0;
for(int i=0;i<n;i++){
if(a.charAt(i) != b.charAt(i)) break;
cnt ++;
}
System.out.println(cnt);
}
}
}
题例有给圆周率的值,草
B. Taisia and Dice
题意
给定 \(3\) 个整数 \(n, s, r\),构造一个数组 \(a\),满足最大值为 \(s - r\),数组的长度为 \(n\),总和为 \(s\)。
思路
从大到小放入能放的最大数即可,上限需要考虑后面是否可以放 \(1\)。
时间复杂度:\(O(n)\)
对应AC代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t, n;
cin >> t;
while(t --){
int s, r;
cin >> n >> s >> r;
int maxx = s - r;
cout << maxx << ' ';
s -= maxx;
for(int i=n-2;i>=0;i--){
int cur = min(6ll, max(1ll, min(maxx, s - i)));
cout << cur << ' ';
s -= cur;
}
cout << '\n';
}
}
反正别用DFS就行
C. Premutation
题意
给定 \(n\) 的一种排列分别去掉每一位后构成的 \(n-1\) 个子序列,输出原排列。
思路
考虑第一位即可。
第一位中出现次数最多的数即为原排列的第一个数,而出现最少的数所在子序列即为原排列去掉第一个数后的子序列,拼起来即可。
时间复杂度:\(O(n)\)
对应AC代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N][N];
#define int long long
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t, n;
cin >> t;
while(t --) {
memset(a, 0, sizeof a);
cin >> n;
int h1, h2, c1 = 0, c2 = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - 1; j++) {
cin >> a[i][j];
}
if(c1 == 0 || h1 == a[i][0]) h1 = a[i][0], c1 ++;
else h2 = a[i][0], c2 ++;
}
int h = c1 > c2 ? h1 : h2;
for(int i=0;i<n;i++){
if(a[i][0] != h){
cout << h << ' ';
for(int j=0;j<n-1;j++) cout << a[i][j] << ' ';
break;
}
}
cout << '\n';
}
}
找规律就行力
D. Matryoshkas
题意
给定一个数组 \(a\),将其拆分成任意数量的子序列,满足子序列升序排序后相邻元素相差 \(1\),且无重复元素,输出子序列的最小数量。
思路
我们可以考虑模拟的做法,将所有子序列抽象为序列中最大的数,作为几堆候选区域:
在升序排序数组 \(a\) 后,我们依次将元素 \(a_i\) 放入,放入前,我们先拿出候选中数值最小的,然后分类讨论:
- 如果满足 \(+1\) 后和\(a_i\) 相同,那么我们直接将元素放入该候选区域;
- 如果两数相同,那么两数均可作为下一个数的可能候选区域,所以新建一个区域并放入该数;
- 否则,那么这个候选区域无法再进行匹配,直接取出并计数。
最后,剩下的堆数加上被取出的堆数即为答案。
当然,为了降低复杂度,考虑使用优先队列。
时间复杂度:\(O(nt \log t),t为队列长度\)
对应AC代码
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int t = scanner.nextInt();
while (t-- > 0) {
int n = scanner.nextInt();
int[] a = new int[n];
PriorityQueue<Integer> q = new PriorityQueue<>(Comparator.comparingInt(o -> o));
for(int i=0;i<n;i++) a[i] = scanner.nextInt();
Arrays.sort(a);
int cnt = 0;
for(int i=0;i<n;i++){
boolean ok = false;
while(!q.isEmpty()){
int now = q.poll();
if(now + 1 == a[i]) {
q.offer(a[i]);
ok = true;
break;
}else if(now == a[i]){
q.offer(a[i]);
q.offer(a[i]);
ok = true;
break;
}
else cnt ++;
}
if(!ok) q.offer(a[i]);
}
System.out.println(cnt + q.size());
}
}
}
其实可以不用这么模拟,太模拟了((
E. Vlad and a Pair of Numbers
题意
给定一个整数 \(n\),满足 \(a \oplus b = \frac{a+b}{2}=n\),输出满足条件的任意一组 \(a,b\)。
思路
我们先来考虑 \(a \oplus b = a+b=t\):
对于任意二进制数 \(t\),当我们从高位向低位遍历时,若遇到 \(1\),那么我们不妨在 \(a\) 的该位填上 \(1\),在 \(b\) 的该位上填上 \(0\),这样即可满足异或运算和加法运算的值一致。
而对于 \(a \oplus b = \frac{a+b}{2}=n\):
我们注意到有除 \(2\) 的运算,该操作等效于将 \(a+b\) 的二进制结果向低位移动一位,此时,若按照上述做法,我们会发现 \(1\) 的位置恰好差了一位,那么我们就希望能出现进位的操作。
考虑到对于二进制运算,\(11+01=100\),那么我们不妨在上述做法的基础上,在 \(1\) 后面一位对应的 \(a,b\) 位置上分别填上 \(1,0\),这样就可以满足我们的需求了。
而显而易见,当 \(1\) 出现在最后一位(奇数),或者有连续的 \(1\) 存在时,即为无解。
时间复杂度:\(O(\log_2n)\)
对应AC代码
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int t = scanner.nextInt();
nxt:
while (t-- > 0) {
int n = scanner.nextInt(); //有连着的1就不行
if(n % 2 == 1){
System.out.println(-1);
continue nxt;
}
int a = 0, b = 0;
boolean f = false;
for(char e : Integer.toBinaryString(n).toCharArray()){
int p = e - '0';
if (f) {
if (p == 1) {
System.out.println(-1);
continue nxt;
} else {
a = a * 2 + 1;
b = b * 2 + 1;
f = false;
}
} else {
f = p == 1;
a *= 2;
b *= 2;
if(f) a ++;
}
}
System.out.printf("%d %d\n", a, b);
}
}
}
真的不是找规律题么((
F. Timofey and Black-White Tree
题意
对于一个 \(n\) 个点 \(n-1\) 个边的无向无环图,给定操作顺序,将 \(n\) 个点按照顺序涂成黑色,输出从第 \(2\) 个操作开始,对于每次操作后,所有任意两个黑点的距离的最小值。
思路
我们可以考虑树形 \(dp\) 的写法,其中 \(dp[i]\) 表示第 \(i\) 个点 在涂色前 该点的所有子节点中 黑色的点到该点的最短距离。
难道我们要把子节点全都遍历一遍吗?显然不用。
这是一个无向无环图,那么显然一个点只有一个父节点,只要根节点确定了,我们构建一个数组即可。
因此,我们不妨从要涂色的点开始,向上 \(Dfs\) 到根节点,在遍历的同时记录当前已经遍历的边数 \(st\)。在遍历到节点 \(i\) 时,我们以这个点为跳板,用 \(dp[i]+st\) 更新最终答案,并将 \(dp[i]\) 更新为 \(\min(dp[i],st)\) 即可。
深搜剪枝
- 根节点的确定:在绝大多数情况下,取子节点最多的点作为根节点是更优的,这样可以让链条结构更多,从而一定程度降低时间复杂度;
- 只遍历父节点
- \(st>=ans\) 时,结束遍历:显然,我们需要求答案的最小值,那么就算我们继续遍历,最后更新的 \(dp\) 值是一定大于等于 \(ans\) 的,因而我们就没必要 让其他将要涂黑的点 以这个点为跳板 来更新 \(ans\) 了。而恰恰因为我们至少遍历了 \(ans-1\) 个,可以保证 最小路径所在边 一定被我们遍历过了,因而答案没有问题。
整体来看
- 确定根节点;
- 从根节点开始向下 \(Dfs\) 一遍,求出每个点的父节点;
- 依次遍历要涂黑的点,向上 \(Dfs\) 并更新 \(dp\) 值。
时间复杂度:不会分析
对应AC代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010, inf = 0x3f3f3f3f;
#define int long long
vector<int> e[N];
int dp[N], c[N], root, ans, vis[N], f[N];//cut2: 只遍历爹,因为爹只有一个(无环
void dfs1(int fa){
for(int p : e[fa]){
if(vis[p]) continue;
vis[p] = true;
f[p] = fa;
dfs1(p);
}
}
void dfs2(int child, int st){
if(st >= ans) return; //cut3: 步数那么多就没必要继续走,防止每次都被卡O(n)
if(dp[child] != inf) ans = min(ans, dp[child] + st);
dp[child] = min(st, dp[child]);
if(child != root) dfs2(f[child], st + 1);
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t, n;
cin >> t;
while(t --) {
cin >> n >> c[0];
for(int i=1;i<n;i++) {
cin >> c[i];
dp[i] = inf;
vis[i] = false;
e[i].clear();
}
dp[n] = inf;
vis[n] = false;
e[n].clear();
int maxx = -1;
root = 1; //cut1: 根节点选分支最多的
for(int i=1;i<n;i++){
int u, v;
cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
int s1 = e[u].size(), s2 = e[v].size();
if(max(s1, s2) > maxx) {
maxx = max(s1, s2);
root = s1 > s2 ? u : v;
}
}
dfs1(root);
ans = inf;
dfs2(c[0], 0);
for(int i=1;i<n;i++){
dfs2(c[i], 0);
cout << ans << ' ';
}
cout << '\n';
}
}
我趣,怎么一遍过