G.Horse Drinks Water(模拟,数学) 时间复杂度:$O(t)$ 题意 将军饮马问题:马处于点$(x_G,y_G)$,将军处于点$(x_T,y_T)$,河水位于x轴正半轴与y轴的正半轴 上,求将军饮马的最短距离.
数据范围
$ 0$ $\le x_G,y_G,x_T,y_T \le 10^9$
思路 签到题,由数据范围知,将军和马一定同处于第一象限 ,所以将任一点的坐标转换到第二象限,求 直线两点的距离 即可。注意浮点数输入输出。
Code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <bits/stdc++.h> #define endl '\n' using i64 = long long; using namespace std; signed main(){ std::ios::sync_with_stdio(false); cin.tie(0); int t; cin >> t; while (t--){ int xg, yg, xt, yt; cin >> xg >> yg >> xt >> yt; xg *= -1; //式子一定要加long long,不然局部int就炸了 double res1 = sqrt(1LL * (xg - xt) * (xg - xt) + 1LL * (yg - yt) * (yg - yt)); xg *= -1, yg *= -1; double res2 = sqrt(1LL * (xg - xt) * (xg - xt) + 1LL * (yg - yt) * (yg - yt)); double ans = min(res1, res2); cout << fixed << setprecision(9) << ans << endl; } return 0; }
I.Friends(双指针+set存图) 题意 给定一张$n$个点$m$条边的图,定义区间$[l,r]$为点$l$到点$r$间每个点之间都两两有边 ,请问满足该区间条件的总数量 是多少?
数据范围
$1 \le n,m \le 10^6$
$1 \le l \le r \le n$
思路 题意可以转化为求区间各个节点是否构成一张完全图 ,对于区间$[l,r]$而言,当$l=r$时,只有一个孤立的点,显然满足条件;当$l < r$时,不难发现,当$[l,r-1]$满足条件时,只需满足节点$r$与节点$i \in [l,r-1]$均有边,也就是该点连通在区间中的边的个数是$r-l$,就能保证区间$[l,r]$是完全图。例如区间$[3,6]$,已知区间$[3,5]$互为两两有边,只需保证点$6$与$3,4,5$均有边即可. 对区间的数量枚举,我们只需要找到每一个左节点对应的 右边最大满足下标 即可,不难发现该右端点一定是单调的 ,所以使用 双指针 去维护区间信息,统计各个枚举到的左端点对答案的贡献. 对于存图,由于判断完全图的方法是统计区间中 小于R的边的个数 ,所以每次输入$u,v$时(题设已保证$u < v$)只需在$v$点保存$u$点的信息即可,自此,每次判断,也只要取$v$点的邻接表节点。同时各节点不能重复,可以使用set容器 进行存图。 关于复杂度,因为判断只会在右指针 进行 ,只遍历一次就会取出所有的连通点,也就是恰好会取出所有边,那么复杂度就是严格的$m$,因$m<10^6$,所以整体复杂度是合法的.
Code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include<bits/stdc++.h> #define endl '\n' using i64 = long long; using namespace std; signed main(){ std::ios::sync_with_stdio(false);cin.tie(0); int n, m; cin >> n >> m; set<int> st[n + 1]; for (int i = 1; i <= m;i++){ int u, v; cin >> u >> v; st[v].insert(u); } auto check = [&](int L, int R) -> bool //判断区间[L,R]是否是完全图 { if(L==R) return 1; int cnt = 0; for(auto it:st[R]) if(it>=L) cnt++; return cnt == R - L; }; i64 ans = 0; for (int i = 1, j = 1; i <= n;i++){ while (j <= n && check(i, j)) j++; //cout << i << ' ' << j << endl; ans += j - i; //贡献区间数量为右端点-左端点 } cout << ans << endl; return 0; }
C.Sort4(思维) 时间复杂度:$O(n)$ 题意 给定一个长度为$n$的排列,在一次操作中,最多 可以 交换(调整)任意四个元素 的位置,请问最少经过多少次操作可以将整个排列升序?
数据范围
$1 \le n \le 10^6$
$\sum n \le 10^6$
思路 根据排列的性质,如果是升序排列,那么每一个数一定满足$p_i=i$,我们从下标到值的映射角度看,整个排列实际上是由多种$i \rightarrow p_i$环组成的 ,且这些环一定相互独立 。 设环的长度为$len$,易证:
顾名思义,$len=1$的环就是$p_i=i$本身,这些点显然无需操作
当$len=2$,只需两两交换位置即可,一次操作能处理 最多两个环
当$len=3 \:or \:4$时,一次操作只能 处理一个环
当$len \ge 4$时,手玩数据后不难发现,每次最多能够消除三个点 ,也就是$len=len-3$;如$p_1=3,p_3=4,p_4=6,p_6=7,p_7=1$,也就是构成了$1 \rightarrow 3 \rightarrow 4 \rightarrow 6 \rightarrow 7$的环,此时我们将前四个元素调整到合适位置,那么$p_3=3,p_4=4,p_6=6$且$p_1=7,p_7=1$,只剩下$1 \rightarrow 7$的$len=2$的环,所以每次应当最多使得环长减去3
使用一次DFS求出所有环的长度 $len$,设$len=2$的环为$len2$,当遇到$len=2$时$len2$的计数值加一;设$len=3 \:or \:4$的环为$len3$,由于$len=3 \: or \:4$的环都需要耗费一次操作,遇到时同样将$len3$的计数值加一;当$len \ge 4$时,
综上,答案为$\lceil {\dfrac{len2}{2}} \rceil +len3$.
Code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include<bits/stdc++.h> #define endl '\n' using i64 = long long; using namespace std; signed main(){ std::ios::sync_with_stdio(false);cin.tie(0); int t;cin >> t; while(t--){ int n; cin >> n; vector<int> a(n + 1); vector<bool> vis(n + 1); for (int i = 1; i <= n;i++) cin >> a[i]; int cnt, ans = 0; auto dfs = [&](auto dfs, int x) -> void { vis[x] = 1, cnt++; if(!vis[a[x]]) dfs(dfs, a[x]); }; int len2 = 0, len3 = 0; for (int i = 1; i <= n;i++){ cnt = 0; //环的长度 if (vis[i] || a[i] == i) continue; dfs(dfs, i); //cout << i << ' ' << cnt << endl; if(cnt==2) len2++; else if(cnt==3) len3++; else if(cnt%3==0||cnt%3==1) len3 += cnt / 3; else len2++, len3 += (cnt - 2) / 3; } ans = (len2 + 1) / 2 + len3; cout << ans << endl; } return 0; }
H.Yet Another Origami Problem(数学) 题意 给定一个长度为$n$的数组$a$,每次选择一个索引$p$,并执行以下任意次数(可为零)的操作: i.对于所有$i$,如$a_i \le a_p$,让$a_i \leftarrow a_i+2(a_p-a_i)$ ii.对于所有$i$,如$a_i \ge a_p$,让$a_i \leftarrow a_i-2(a_i-a_p)$ 请问经过任意次操作后,最小化数组的最大和最小元素的差值,该差值的最小值是多少?
数据范围
$1 \le n \le 10^5$
$0 \le a_i \le 10^{16}$
$\sum n \le 5 \times 10^5$
思路 结论$gcd(a_2-a_1,a_3-a_2,…diff_n)diff_i=a_i-a_{i-1}$秒了.
Code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include<bits/stdc++.h> #define endl '\n' using i64 = long long; using namespace std; signed main(){ std::ios::sync_with_stdio(false);cin.tie(0); int t; cin >> t; while(t--){ int n; cin >> n; vector<i64> a(n + 1), b(n + 1); for (int i = 1; i <= n;i++) cin >> a[i]; if (n == 1){ //其实可以不特判n=1 cout << "0" << endl; continue; } i64 g = 0; for (int i = 2; i <= n;i++){ b[i] = a[i] - a[i - 1]; g = __gcd(g, b[i]); } cout << g << endl; } return 0; }
A.LCT(带权并查集) 前置知识:带权并查集 题意 给定一棵有根的树,树上有$n$个节点和$n-1$条边。接下来$n-1$行,有三个整数$a_i$,$b_i$和$c_i$,表示节点$a_i$是节点$b_i$的父节点,并且进行一次询问,$c_i$表示在由当前$i$条边构成的森林中,从节点$c_i$开始的最长简单路径的长度。保证不存在$j$使得$1 \le j \le i$ 和 $b_j=c_i$,即$c_i$是森林中一棵树的根。
数据结构
$2 \le n \le 10^6$
$1 \le a_i,b_i,c_i \le n,a_i \ne b_i$
$\sum n \le 10^6$
思路 由题意知,每次合并的时候一定是将一棵树的根合并到另一棵树的结点上 的,并询问某棵树(不一定是当前合并后)的最大深度. 很明显的思路是设$h[i]$为节点$i$到当前这棵树的根节点的距离,当两棵树合并时(假定树$b$合并到树$a$的一节点)只需将树$b$的所有深度加上树$a$当前结点的深度加一;用以维护$h[i]$在当前树上的深度以及合并的操作,很明显的做法就是带权并查集!对于合并,对树$b$进行集体增加深度的操作,只需并查集find操作的时候,同时递归地加上父节点深度即可. 由于求的答案是根节点的最大路径长度,我们设$ans[i]$为根节点的最大深度,当每次节点$a_i$和$b_i$合并时,不难想到转移方程$ans[fa[a_i]]=Max(ans[fa[a_i]],h[a_i]+ans[b_i]+1)$.
Code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include<bits/stdc++.h> #define endl '\n' using i64 = long long; using namespace std; signed main(){ std::ios::sync_with_stdio(false);cin.tie(0); int t;cin >> t; while(t--){ int n;cin >> n; vector<int> h(n + 1); // h[i]表示节点i距离所在树根的距离 vector<int> ans(n + 1); // ans[i]表示根节点i包含的最大深度 vector<int> fa(n + 1); auto find = [&](auto find, int x) -> int { if (x == fa[x]) return x; int f = find(find, fa[x]); h[x] += h[fa[x]]; return fa[x] = f; }; iota(fa.begin(), fa.end(), 0); for (int i = 1; i < n; i++){ int u, v, root; cin >> u >> v >> root; // u是v的父亲节点 int fu = find(find, u), fv = find(find, v); h[v] = h[u] + 1; //必须率先对"子树"根节点赋值 ans[fu] = max(ans[fu], h[u] + ans[v] + 1); fa[fv] = fu; cout << ans[root] << ' '; } cout << endl; } return 0; }