茶7.8-7.12 Mon Find the Different Ones!_923D
输入 T(≤1e4) 表示 T 组数据。所有数据的 n 之和 ≤2e5,q 之和 ≤2e5。 每组数据输入 n(2≤n≤2e5) 和长为 n 的数组 a(1≤a[i]≤1e6),下标从 1 开始。 然后输入 q(1≤q≤2e5) 和 q 个询问,每个询问输入两个数 L R(1≤L<R≤n)。
对于每个询问,输出在 [L,R] 内的两个下标 i 和 j,满足 a[i] ≠ a[j]。 如果不存在,输出 “-1 -1”。
样例太长,请在原题查看。
Sol:
定义 left[i] 表示在 a[i] 左侧的不等于 a[i] 的最近元素下标。
递推:如果 a[i] = a[i-1] 则 left[i] = left[i-1],否则 left[i] = i-1。
回答询问时,如果 left[R] < L 则输出 -1 -1,否则输出 left[R] 和 R。
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 void solve () { int n; cin>>n; vi a (n) ; for (int i=0 ;i<n;i++) cin>>a[i]; vi nxt (n) ; nxt[n-1 ]=n; for (int i=n-2 ;i>=0 ;i--){ nxt[i] = a[i] == a[i+1 ] ? nxt[i+1 ] : i+1 ; } int q; cin>>q; while (q--){ int l,r; cin>>l>>r; l--; if (nxt[l]<r){ cout<<l+1 <<" " <<nxt[l]+1 <<"\n" ; }else { cout<<-1 <<" " <<-1 <<'\n' ; } } }
Tue https://codeforces.com/problemset/problem/1670/C
输入 T(≤1e5) 表示 T 组数据。所有数据的 n 之和 ≤5e5。 每组数据输入 n(1≤n≤1e5) 和两个 1~n 的排列 a 和 b, 然后输入数组 d,保证 d[i] 是 {0,a[i],b[i]} 中的一个。
构造一个 1~n 的排列 c,满足: 如果 d[i]>0,那么 c[i] 必须是 d[i],否则 c[i] 可以是 a[i],也可以是 b[i]。
你能构造出多少个不同的排列 c?模 1e9+7。 保证至少能构造一个排列 c。
样例太长,请在原题查看。
Sol:
从 a[i] 向 b[i] 连边,我们会得到若干个环(没有树枝,因为 a 和 b 都是排列)。
对于长度大于 1 的环,如果不包含 d[i] > 0 的点,那么有两种选法。
比如 1-2-3-1 这个环,每条边可以分别选 1,2,3 也可以分别选 2,3,1。
所以答案就是 2^m,其中 m 是长度大于 1 的不含 d[i]>0 的环的个数。
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 void solve () { int n; cin >> n; vector<int > a (n) ; for (int j = 0 ; j < n; j++){ cin >> a[j]; a[j]--; } vector<int > b (n) ; for (int j = 0 ; j < n; j++){ cin >> b[j]; b[j]--; } vector<int > d (n) ; for (int j = 0 ; j < n; j++){ cin >> d[j]; d[j]--; } vector<bool > used (n, false ) ; bool ok = true ; for (int j = 0 ; j < n; j++){ if (d[j] != -1 ){ if (used[d[j]]){ ok = false ; } used[d[j]] = true ; } } if (!ok){ cout << 0 << "\n" ; } else { vector<vector<int >> E (n); for (int j = 0 ; j < n; j++){ E[a[j]].push_back (b[j]); E[b[j]].push_back (a[j]); } long long ans = 1 ; vector<bool > used2 (n, false ) ; for (int j = 0 ; j < n; j++){ if (!used2[j]){ used2[j] = true ; queue<int > Q; Q.push (j); int cnt = 0 ; bool ok2 = true ; while (!Q.empty ()){ int v = Q.front (); Q.pop (); cnt++; if (used[v]){ ok2 = false ; } for (int w : E[v]){ if (!used2[w]){ used2[w] = true ; Q.push (w); } } } if (ok2 && cnt >= 2 ){ ans *= 2 ; ans %= MOD; } } } cout << ans << "\n" ; } }
Wed Moving Both Hands
输入 n(2≤n≤1e5) m(0≤m≤2e5),表示一个 n 点 m 边的有向图。节点编号从 1 开始。保证图中无自环和重边。 然后输入 m 条边,每条边输入 x y w(1≤w≤1e9),表示一条从 x 到 y 的边权为 w 的有向边。
定义 f(i) 为:有甲乙两人,分别从 1 和 i 出发,到达同一个点,两人用时之和的最小值。如果两人无法到达同一个点,则 f(i) = -1。 输出 f(2), f(3), …, f(n)。
输入 5 7 1 2 2 2 4 1 4 1 4 2 5 3 5 4 1 5 2 4 2 1 1 输出 1 -1 3 4
Sol:
分层图最短路。
建图:把每条有向边及其反向边加到图中,用一个额外变量 inv 表示这条边是否为反向边。
从 1 到 i 的路径分成两段:
从 1 到 x,走原图上的边。(记作状态 0)
从 x 到 i,走反向边。(记作状态 1)
由于到达每个节点时,都有两个状态,所以相当于在有 2n 个节点的图上跑 Dijkstra 算法。
Dijkstra 算法介绍
类似状态机 DP,在 Dijkstra 的优先队列中,额外记录一个状态 inv,如果 inv=0 表示当前在走原图上的边,如果 inv=1 表示当前在走反向边。
如果当前状态是 0,那么可以转移到 0 或者 1。
如果当前状态是 1,那么只能转移到 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 35 36 37 38 39 40 41 42 void solve () { int n, m; cin >> n >> m; vector<vector<array<int , 3> > > adj (n + 1 ); for (int i = 0 ; i < m; i++) { int u, v, w; cin >> u >> v >> w; adj[u].push_back ({v, w, 0 }); adj[v].push_back ({u, w, 1 }); } const int64_t inf = 1e18 ; vector<vector<int64_t >> dist (n + 1 , vector <int64_t >(2 , inf)); vector<vector<int >> st (n + 1 , vector <int >(2 )); priority_queue<tuple<int64_t , int , int >, vector<tuple<int64_t , int , int >>, greater<tuple<int64_t , int , int >>> hp; hp.emplace (0 , 1 , 0 ); dist[1 ][0 ] = 0 ; while (!hp.empty ()) { auto [d, x, rev] = hp.top (); hp.pop (); if (st[x][rev]) { continue ; } st[x][rev] = true ; for (auto & [y, w, e] : adj[x]) { if (!rev) { if (dist[y][e] > dist[x][0 ] + w) { dist[y][e] = dist[x][0 ] + w; hp.emplace (dist[y][e], y, e); } } else { if (dist[y][1 ] > dist[x][1 ] + w && e == 1 ) { dist[y][1 ] = dist[x][1 ] + w; hp.emplace (dist[y][1 ], y, e); } } } } for (int i = 2 ; i <= n; i++) { cout << (min (dist[i][0 ], dist[i][1 ]) == 1e18 ? -1 : min (dist[i][0 ], dist[i][1 ])) << " \n" [i == n]; } }
Thu https://codeforces.com/problemset/problem/582/B
输入 n(1≤n≤100) t(1≤t≤1e7) 和长为 n 的数组 a(1≤a[i]≤300)。
把 t 个一样的数组 a 拼接在一起,得到长为 t*n 的数组 b,满足 b[i] = b[i+n]。 输出 b 的 LIS 长度。注意这里的 LIS 是非降的,允许元素相等。
输入 4 3 3 1 4 2 输出 5
Sol:
贪心+二分求 LIS
如果 t <= n,那么直接求长为 tn 的数组的 LIS。也就是对数组 a 循环 t 次。
如果 t > n,只需要循环 n 次。
设最后一轮循环(第 n 轮循环)二分的数组 f 的长度增加了 d,那么有如下结论:
接下来的 t-n 轮循环是不需要跑的,保证每轮循环 f 数组的长度都会增加 d。
例如 a=[2,3,1],前三轮循环,找到的 LIS 为 [2,3] [1,2,3] [1,1,2,3]
后面每轮循环都只会让 f 的长度增加 1。 相当于每多一轮循环,中间就插入了一个 1。
猜想:只需要循环 sqrt(n) 左右次即可。
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 void solve () { int n, t, d; cin >> n >> t; vi a (n) ; FOR (i, 0 , n-1 ) cin >> a[i]; vi f; for (int i = 0 ; i <= n && t > 0 ; i++) { int m = f.size (); for (int x : a) { auto j = lower_bound (f.begin (), f.end (), x + 1 ); if (j != f.end ()) { *j = x; } else { f.push_back (x); } } d = f.size () - m; t--; } cout << f.size ()+t*d << endl; }
Fri Maximizing Root_848F
输入 T(≤5e4) 表示 T 组数据。所有数据的 n 之和 ≤2e5。 每组数据输入 n(2≤n≤1e5) k(0≤k≤n) 和长为 n 的数组 a(1≤a[i]≤1000)。 然后输入一棵无向树的 n-1 条边,节点编号从 1 开始。 根节点为 1。节点 i 的点权为 a[i]。
你可以执行如下操作至多 k 次: 选择一个没有操作过的节点 v 和一个整数 x,其中 x 必须满足 x 是子树 v 中所有点权的公约数。然后把子树 v 中的每个点权都乘上 x。
输出操作后,a[1] 最大是多少。
输入 2 5 2 24 12 24 6 12 1 2 1 3 2 4 2 5 5 3 24 12 24 6 12 1 2 1 3 2 4 2 5 输出 288 576
Sol :
提示 1:为了让答案尽量大,从上到下操作比较好,还是从下到上操作比较好?
提示 2:答案一定是 a[1] 乘以它的一个因子。枚举这个因子 d。
提示 3:DFS 这棵树,什么情况下,子树是不需要操作的?什么情况下,子树只需要操作一次?什么情况下,子树只需要操作两次?
灵神题解
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 void solve () { vector<vector<int > > divisors (mx+1 ,vector <int >()); for (int i = mx; i > 0 ; i--) { for (int j = i; j <= mx; j+=i) { divisors[j].push_back (i); } } vi ceilSqrt (mx+1 ) ; for (int i = 1 ;i <= mx; i++) { ceilSqrt[i] = 1 ; int x = i; for (int p = 2 ; p*p <= x; p++) { for (int p2 = p*p; x%p2 == 0 ; x /= p2) { ceilSqrt[i] *= p; } if (x % p == 0 ) { ceilSqrt[i] *= p; x /= p; } } if (x > 1 ) { ceilSqrt[i] *= x; } } int n , t, k; cin >> t; while (t--) { cin >> n >> k; vi a (n) ; FOR (i, 0 , n-1 ) cin >> a[i]; vector<vector<int > > g (n, vector <int >()); for (int i = 1 ;i < n; i++) { int v,w; cin >> v >> w; v--; w--; g[v].push_back (w); g[w].push_back (v); } if (k == 0 ) { cout << a[0 ] << endl; continue ; } vi subGcd (n) ; std::function<void (int , int )> dfs0; dfs0 = [&](int v, int fa) -> void { subGcd[v] = a[v]; for (int w : g[v]) { if (w != fa) { dfs0 (w, v); subGcd[v] = gcd (subGcd[v], subGcd[w]); } } }; dfs0 (0 , -1 ); int cnt; std::function<void (int , int , int )> dfs; dfs = [&](int v, int fa, int d) -> void { if (subGcd[v] % d == 0 ) { return ; } if (subGcd[v]*subGcd[v]%d == 0 ) { cnt++; return ; } if (g[v].size () == 1 || a[v]*a[v]%d > 0 ) { cnt = 1e9 ; return ; } for (auto w : g[v]) { if (w != fa) { dfs (w, v, ceilSqrt[d]); } } cnt++; }; for (auto d : divisors[a[0 ]]) { cnt = 0 ; for (auto v : g[0 ]) { dfs (v, 0 , d); } if (cnt < k) { cout << a[0 ]*d << endl; break ; } } } }