D Chopping Carrots
题意
给你一个长度为 n n n的数组 a a a,让你构造一个数组 p p p,且 p p p中所有的数均属于 [ 1 , k ] [1,k] [1,k],并让你求 m a x 1 ≤ i ≤ n ( ⌊ a i p i ⌋ ) − m i n 1 ≤ i ≤ n ( ⌊ a i p i ⌋ ) max_{1\le i \le n} (\lfloor \frac {a_i}{p_i} \rfloor) - min_{1\le i \le n} (\lfloor \frac {a_i}{p_i} \rfloor) max1≤i≤n(⌊piai⌋)−min1≤i≤n(⌊piai⌋)最小是多少。
思路
-
枚举,贪心
我们枚举最小值 l l l,贪心来看然后从 1 ∼ n 1\sim n 1∼n对于每个 a i a_i ai找到大于等于 l l l的最小的 a i p i \frac {a_i}{p_i} piai即可,由于数据范围很小可以先预处理出每个数可以变成哪些数,然后对于每个 a i a_i ai可以变成的数,二分即可。
由于要求最小的 a i p i \frac{a_i}{p_i} piai且 a i p i ≥ l \frac {a_i}{p_i}\ge l piai≥l即求 l × p i ≤ a i → p i ≤ a i l l\times p_i\le a_i\to p_i\le \frac{a_i}{l} l×pi≤ai→pi≤lai,所以也可以 O ( 1 ) O(1) O(1)的求出。
然后对于所有的取值取个 m a x max max找到当前 l l l对应的 r r r,判断是否可以更新答案即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 3010, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
bool st[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin >> n >> k;
for (int i = 1; i <= n; i ++) cin >> a[i];
vector<int> g[n + 1];
for (int i = 0; i <= 3000; i ++) st[i] = false;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= k; j ++)
if (a[i] / j == 0 && g[i].back() == 0) break;
else if (!g[i].size() || (a[i] / j != g[i].back())) g[i].push_back(a[i] / j), st[a[i] / j] = true;
reverse(g[i].begin(), g[i].end());
}
int res = INF;
for (int tag = 0; tag <= 3000; tag ++) {
if (!st[tag]) continue ;
int mn = tag, mx = -1;
for (int i = 1; i <= n; i ++) {
int l = 0, r = g[i].size() - 1;
while (l < r) {
int mid = l + r >> 1;
if (g[i][mid] >= mn) r = mid;
else l = mid + 1;
}
mn = min(mn, g[i][l]), mx = max(mx, g[i][l]);
}
res = min(res, mx - mn);
}
cout << res << '\n';
}
return 0;
}
思路
- 枚举,整除分块
显然直接枚举 l l l再枚举数不行了,考虑优化枚举 a 1 ∼ a n a_1\sim a_n a1∼an这一个步骤。显然对于每个 l l l来说最终找的 r r r是一定的我们设 m x [ l ] : l 所对应的合法的最小上界是多少 mx[l]:l所对应的合法的最小上界是多少 mx[l]:l所对应的合法的最小上界是多少.
对于 a i a_i ai来说假设能变成 b 1 , b 2 , . . . , b x b_1,b_2,...,b_x b1,b2,...,bx这些数且 b i > b i − 1 b_i>b_{i-1} bi>bi−1,对于任意一个 l l l,则 l l l一定可以插在 b i + 1 ≤ l ≤ b i + 1 b_i + 1\le l \le b_{i+1} bi+1≤l≤bi+1,所以存在某个区间 [ l , l + 1 , . . , l + p ] [l,l+1,..,l+p] [l,l+1,..,l+p]使得, b i + 1 ≤ [ l , l + 1 , . . , l + p ] ≤ b i + 1 b_i + 1\le [l,l+1,..,l+p]\le b_{i+1} bi+1≤[l,l+1,..,l+p]≤bi+1,则 m x [ l ∼ l + p ] mx[l\sim l+p] mx[l∼l+p]的取值都可以是 b i + 1 b_{i+1} bi+1,对于每个 m x [ l ] mx[l] mx[l]的所有可能取值里要取最大的一个 m x [ l ] mx[l] mx[l]才能合法,区间赋值我们可以只对 m x [ l ] mx[l] mx[l]赋值即可,然后枚举 l l l的时候对 m x mx mx数组求前缀和即可维护区间。
由于 l l l从小到大枚举,则 l + p + 1 l+p+1 l+p+1这个数所成立的 m x [ l + p + 1 ] mx[l+p+1] mx[l+p+1]是一定大于 m x [ l ] mx[l] mx[l]因此对于区间赋值,做成后缀赋值是没有影响的,那么枚举每个数再将他分解。
对于一个数 n n n将他除以 1 ∼ n 1\sim n 1∼n的值一定是一段一段的,可以整除分块直接跳到对应的下一段的起点,因此复杂度为 O ( a i a i ) O(a_i\sqrt a_i) O(aiai),就可以接受了。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N], mx[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin >> n >> k;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 0; i <= a[1]; i ++) mx[i] = 0;
for (int i = 1; i <= n; i ++) {
int last = INF;
for (int p = 1; p <= min(a[i], k); p = a[i] / (a[i] / p) + 1) {
int l = a[i] / p;
mx[l + 1] = max(mx[l + 1], last);
last = l;
}
mx[0] = max(mx[0], last);
}
int res = INF, pmx = 0;
for (int i = 0; i <= a[1]; i ++) {
pmx = max(pmx, mx[i]);
res = min(res, pmx - i);
}
cout << res << '\n';
}
return 0;
}
E. Qpwoeirut and Vertices
题意
给你一个 n n n个点 m m m条边的连通图可 q q q次询问,每次询问问你一个区间 [ l , r ] [l,r] [l,r],你需要回答 [ l , r ] [l,r] [l,r]里的任意点对需要经过至少前多少条边两两互达。
思路
- 贪心,启发式合并
对于任意两点我们想让他们经过的边的编号尽量的小,由于边的编号的是不同的,因此该图的最小生成树唯一,所以我们把边的编号当成边权,实际上有用的边就是该图最小生成树上的边。
多组询问,每次问你一个 [ l , r ] [l,r] [l,r]显然是不能枚举其中的点对来解答的,这里启示我们可以找一个等价的子集来枚举,考虑只枚举每个位置和前一个位置的互达的最小编号,对于任意一个不相邻的点对,如果他们互达的最小编号比我们枚举这些位置的最大值小,那么是不需要他的因为求的是最大值,如果比我们枚举的这些位置的最大值大,显然是不可能的,假设是 ( x , y ) (x,y) (x,y)显然我们可以沿着 x → x + 1 → . . . → y x\to x+1\to...\to y x→x+1→...→y这样走下去一定小于等于我们枚举的最大值。
因此我们只需要维护出每个位置和前一个位置的互达最小编号即可,对于查询区间 [ l , r ] [l,r] [l,r]我们只需要求出 [ l + 1 , r ] [l+1,r] [l+1,r]中的最大值即可,这个可以用 s t st st表维护。
那么怎么维护每个位置和前一个位置互达的最小编号呢,暴力的去跑肯定不行,考虑我们用 k r u s k a l kruskal kruskal求最小生成树的时候,枚举当前的边,如果所连的两个点不在一个集合的时候就要合并这两个集合,则我们可以暴力的枚举一个集合里的所有的点,看看他的左右临点是否再另一个集合里,在的话就直接给对应点的最小编号赋值,这样暴力维护也是 O ( n 2 ) O(n^2) O(n2)的,我们只需要将小的集合向大的集合合并,就可以做到 O ( n l o g n ) O(nlogn) O(nlogn)了。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int f[N];
int find(int x) {
return x == f[x] ? x : f[x] = find(f[x]);
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin>> n >> m >> k;
vector<int> sz[n + 1];
vector<int> pre(n + 1);
for (int i = 1; i <= n; i ++) f[i] = i, sz[i].push_back(i);
for (int i = 1; i <= m; i ++) {
int x, y; cin >> x >> y;
int xx = find(x), yy = find(y);
if (xx != yy) {
if (sz[xx].size() > sz[yy].size()) swap(xx, yy);
for (auto t : sz[xx]) {
if (t != 1 && find(t - 1) == yy) pre[t] = i;
if (t != n && find(t + 1) == yy) pre[t + 1] = i;
sz[yy].push_back(t);
}
f[xx] = yy;
}
}
vector<vector<int>> st(n + 1, vector<int>(21));
for (int i = 1; i <= n; i ++) st[i][0] = pre[i];
for (int j = 1; j <= 20; j ++)
for (int i = 1; i + (1 << j) - 1 <= n; i ++)
st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
auto query = [&](int l, int r) {
if (l > r) return 0;
int len = __lg(r - l + 1);
return max(st[l][len], st[r - (1 << len) + 1][len]);
};
while (k --) {
int l, r; cin >> l >> r;
l ++;
cout << query(l, r) << ' ';
}
cout << '\n';
}
return 0;
}