https://ac.nowcoder.com/acm/contest/3007
题解 https://ac.nowcoder.com/discuss/367149?type=101&order=0&pos=1&page=1
A. 配对
题意:A,B两个集合,要使每个元素两两配对,且每对求和,使第K大的和最大。
贪心
只用前K大的数配对,变成使最小的和最大,可以贪心地倒序配对,取每次地最小值,最小值一定最大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <bits/stdc++h> using namespace std;const int N = 100050 ;int a[N], b[N], n, k, ans = 2e8 ;int main () { int i, j; scanf ("%d%d" , &n, &k); for (i = 1 ; i <= n; i++)scanf ("%d" , &a[i]); for (i = 1 ; i <= n; i++)scanf ("%d" , &b[i]); sort (a + 1 , a + n + 1 ); sort (b + 1 , b + n + 1 ); for (i = n; i > n - k; i --) ans = min (ans, a[i] + b[n - k + n + 1 - i]); printf ("%d\n" , ans); return 0 ; }
E. 立方数
题意:对于给定的正整数 N N N ,求最大的正整数 A A A ,使得存在正整数 B B B ,满足 A 3 B = N A^3B=N A 3 B = N ,输入包含 T T T 组数据,1 ≤ T ≤ 10 , 000 1≤T≤10,000 1 ≤ T ≤ 10 , 000 ;1 ≤ N ≤ 1 0 18 1≤N≤10^{18} 1 ≤ N ≤ 1 0 18 。
神仙优化题
显然是要对 N N N 质因数分解,对每个质因数验证是否三次方仍为因数,因此可以只枚举 N 1 / 3 N^{1/3} N 1/3 以内的质因数,但是数据很大,只有优化到 N 1 / 4 N^{1/4} N 1/4 才行,所以考虑只枚举 N 1 / 4 N^{1/4} N 1/4 以内的质因数,枚举完还剩下的质因数一定大于 N 1 / 4 N^{1/4} N 1/4 ,那么最多只会还有一个因数,满足它的三次方仍为因数,因为如果有两个,每个因数最小为 N 1 / 4 N^{1/4} N 1/4 ,乘积最小为 N 6 / 4 N^{6/4} N 6/4 。
并且最后剩下的那个数如果存在因数满足它的三次方仍为因数,那么剩下的那个数一定是完全立方数,而那个因数就是它唯一的因数。它的因数最小为 N 1 / 4 N^{1/4} N 1/4 ,立方最小为 N 3 / 4 N^{3/4} N 3/4 ,如果还有一个不同的因数,那个数一定大于 N 1 / 4 N^{1/4} N 1/4 ,那么乘积就大于 N N N 。
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 #include <bits/stdc++.h> using namespace std;#define ll long long typedef pair<int , int >pii;const int INF = 0x3f3f3f3f ;const int N = 31700 ;int T;ll x, pri[N], pr_tr[N], pr_qu[N]; bool vis[N];int main () { scanf ("%d" , &T); for (int i = 2 ; i < N; i++) { if (vis[i])continue ; pri[++pri[0 ]] = 1ll * i; pr_tr[pri[0 ]] = 1ll * i*i*i; pr_qu[pri[0 ]] = 1ll * i*pr_tr[pri[0 ]]; for (int j = i * i; j < N; j += i)vis[j] = 1 ; } while (T--) { scanf ("%lld" , &x); ll ans = 1 ; for (int i = 1 ; pr_qu[i] <= x; i++) { if (x%pri[i] != 0 )continue ; while (x%pr_tr[i] == 0 ) { ans *= pri[i]; x /= pr_tr[i]; } while (x%pri[i] == 0 )x /= pri[i]; } ll L = 1 , R = 1000000 ; while (L < R) { ll mid = L + R >> 1 ; if (mid*mid*mid >= x)R = mid; else L = mid + 1 ; } if (L*L*L == x)ans *= L; printf ("%lld\n" , ans); } return 0 ; }
I. 云
题意:平面上有两类矩形,A类都在第三象限,B类都在第一象限,都不与坐标轴相交。A类向右移动,B类向下移动,速度都相同。问有几对A和B矩形相交。
神仙思维题
扫描线
两种矩形都运动不好处理,所以考虑相对运动,B不动,A向右上动,由运动合成可知A的路径为 y = x y=x y = x 方向,原先的两个矩形相遇等价于A向 y = x y=x y = x 方向移动能碰到B,又等价于它们在 y = − x y=-x y = − x 上的投影线段相交 。
由于一个在第三,一个在第一象限,所以A,B的投影点的x坐标一定不同,原先平面上的点只要映射x坐标即可。投影点的x坐标为 ( x 1 − y 1 ) / 2 (x_1-y_1)/2 ( x 1 − y 1 ) /2 ,除2也不用了。
线段相交可以用扫描线处理。
按照左端点排序,排序时一定坐标相同的把左端点放前面,遇到右端点就-1,左端点就+1,每次只要遇到左端点,还剩下的值就是还没有闭合的线段,一定与当前线段相交,直接加入答案。
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 #include <bits/stdc++.h> using namespace std;#define ll long long typedef pair<int , int >pii;const int INF = 0x3f3f3f3f ;const int N = 1e6 + 10 ;int n, m, t;struct X { int x, lr, type; bool operator <(const X& b) { return x == b.x ? lr > b.lr:x < b.x; } }a[N]; int cnt[2 ];int main () { scanf ("%d%d" , &n, &m); int x1, y1, x2, y2; for (int i = 0 ; i < n; i++) { scanf ("%d%d%d%d" , &x1, &y1, &x2, &y2); a[t++] = X{ x1 - y1,1 ,1 }; a[t++] = X{ x2 - y2,-1 ,1 }; } for (int i = 0 ; i < m; i++) { scanf ("%d%d%d%d" , &x1, &y1, &x2, &y2); a[t++] = X{ x1 - y1,1 ,0 }; a[t++] = X{ x2 - y2,-1 ,0 }; } sort (a, a + t); ll ans = 0 ; for (int i = 0 ; i < t; i++) { cnt[a[i].type] += a[i].lr; if (a[i].lr == 1 )ans += cnt[a[i].type ^ 1 ]; } printf ("%lld\n" , ans); return 0 ; }
I. 导航系统
题意:矩阵 dij 表示树上点i和点j的最短距离,问是否存在这样的树,若存在则输出所有边权。
假设存在,则从这个矩阵上可以找到所有边,特征就是直接相连的两条边一定满足不存在k,使得 d [ i ] [ j ] = d [ i ] [ k ] + d [ k ] [ j ] d[i][j]=d[i][k]+d[k][j] d [ i ] [ j ] = d [ i ] [ k ] + d [ k ] [ j ] ,所以直接 n 3 n^3 n 3 枚举i,j,k,判断即可。
但是本题有个坑,存在边权为0,(虽然不知道这城市怎么建的),会导致重复加边,比如:1-2-3-4,12边权为0,23边权为2,34边权为0,则会把13,14,24都误当成边加进去。所以要把距离为0的城市放在一个并查集里维护,并且一个并查集只有根连出去边,其他的点只和根相连,所以总边数一定=根连出的边数+非根的点数。
题解里说把矩阵所有元素都作为边连图,原树一定是图的Kruskal生成树,求完生成树再判断。复杂度相同,也是一种新的方法。
假设Kruskal按照边权排序后先枚举到边 (u,v) ,则 (u,v) 一定是原树上的边,否则一定存在 (u,k),(k,v),且 (u,k),(k,v) 一定小于 (u,v)。而则以此类推,Kruskal枚举到的所有边都加入生成树,且都是原树上的边,直到联通,那么Kruskal生成树就等于原树。这里就不用额外处理0了。
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 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <bits/stdc++.h> using namespace std;#define ll long long typedef pair<int , int >pii;const int INF = 0x3f3f3f3f ;const int maxn = 1e5 + 10 ;int n;int p[maxn], rk[maxn];int find (int x) { return p[x] == x ? x : p[x] = find (p[x]); }void uni (int x, int y) { x = find (x), y = find (y); if (x == y)return ; if (rk[x] < rk[y])p[x] = y; else { p[y] = x; if (rk[x] == rk[y])rk[x]++; } } int d[510 ][510 ];vector<int >es; int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; i++)p[i] = i; for (int i = 1 ; i <= n; i++)for (int j = 1 ; j <= n; j++) { scanf ("%d" , &d[i][j]); if (!d[i][j])uni (i, j); } for (int i = 1 ; i <= n; i++) { for (int j = i + 1 ; j <= n; j++) { if (d[i][j] != d[j][i]) { puts ("No" ); return 0 ; } if (p[i] != i || p[j] != j)continue ; bool flg = 1 ; for (int k = 1 ; k <= n; k++) { if (d[i][k] == 0 || d[j][k] == 0 )continue ; if (d[i][j] > d[i][k] + d[k][j]) { puts ("No" ); return 0 ; } else if (d[i][j] == d[i][k] + d[k][j]) { flg = 0 ; break ; } } if (flg)es.push_back (d[i][j]); } } int cnt = 0 ; for (int i = 1 ; i <= n; i++)if (p[i] != i)cnt++; if ((int )es.size () + cnt != n - 1 )puts ("No" ); else { puts ("Yes" ); sort (es.begin (), es.end ()); for (int i = 0 ; i < cnt; i++)puts ("0" ); for (int i : es)printf ("%d\n" , i); } return 0 ; }