1635A - Min Or Sum
题意:给定一个序列 a a a,每次可以将 a i a_i ai 和 a j a_j aj 替换成 x x x 和 y y y,要求 a i ∣ a j = x ∣ y a_i\mid a_j = x\mid y ai∣aj=x∣y,问若干次操作后能达到的最小的序列和。
注意到将 ( a i , a j ) → ( a i ∣ a j , 0 ) (a_i, a_j)\to (a_i\mid a_j, 0) (ai,aj)→(ai∣aj,0) 是最优的做法,我们可以通过这样的操作,让尽可能多的数字等于 0 0 0,然后保留一个 a 1 ∣ a 2 ∣ ⋯ ∣ a n a_1\mid a_2\mid \cdots \mid a_n a1∣a2∣⋯∣an。
所以 a 1 ∣ a 2 ∣ ⋯ ∣ a n a_1\mid a_2\mid \cdots \mid a_n a1∣a2∣⋯∣an 即为答案。
1635B - Avoid Local Maximums
题意:给定一个序列 a a a,每次可以将一个 a i a_i ai 改成任意正整数 x x x,问最少多少次这样的操作后,整个序列不存在 local maximum,即不存在 i i i 满足 a i − 1 < a i > a i + 1 a_{i-1} < a_i>a_{i+1} ai−1<ai>ai+1。 n = 2 × 1 0 5 n = 2\times 10^5 n=2×105。
div2 B 题,考虑有没有什么贪心做法。
我们注意到,一次操作最多能够消除两个 local maximums,而且这两个 local maximum 一定相邻。
所以考虑如下贪心算法,从左往右扫,如果遇到一个 local maximum a i a_i ai,则将 a i + 1 ← max ( a i , a i + 2 ) a_{i + 1}\leftarrow \max(a_{i}, a_{i + 2}) ai+1←max(ai,ai+2)。不难发现这样最多就可以同时干掉两个 local maximum,因此其是最优的。
const int maxn = 2e5 + 5;
int a[maxn], n;
int main() {
int T; read(T);
while (T--) {
read(n);
FOR(i, 1, n) read(a[i]);
int ans = 0;
FOR(i, 2, n - 1)
if (a[i] > a[i + 1] && a[i] > a[i - 1])
a[i + 1] = max(a[i], a[i + 2]), ++ans;
print(ans);
FOR(i, 1, n) print(a[i], ' ');
putchar('\n');
}
return output(), 0;
}
1635C - Differential Sorting
题意:给定一个序列 a a a,每次操作可以选择 1 ≤ x < y < z ≤ n 1\le x < y < z\le n 1≤x<y<z≤n,然后 a x ← a y − a z a_x\leftarrow a_y - a_z ax←ay−az,构造一个方案使得整个序列不降。 n = 2 × 1 0 5 n = 2\times 10^5 n=2×105。
我们注意到,选择的三个下标一定要满足 x < y < z x < y < z x<y<z,所以发现第一个性质:若 a n − 1 > a n a_{n-1}>a_n an−1>an,则无解,因为我们没办法修正 a n − 1 a_n - 1 an−1。接下来,我们注意到,若 a n ≥ 0 a_n\ge0 an≥0,则我们可以将前面所有数字修正为 a n − 1 − a n a_{n-1} -a_n an−1−an。否则无解,除非序列一开始就不降。
证明:假设 a n < 0 a_n<0 an<0 且序列初始不满足不降,并且我们用了 m m m 步完成排序。假设我们最后一步操作是 ( x m , y m , z m ) (x_m, y_m, z_m) (xm,ym,zm),不难发现为了保证不降,所有元素都是负值,故 a z m a_{z_m} azm 一定 < 0 < 0 <0,但是 a x m = a y m − a z m > a y m a_{x_m} = a_{y_m} - a_{z_m}>a_{y_m} axm=aym−azm>aym,假设不成立。
const int maxn = 2e5 + 5;
int a[maxn], n;
int main() {
int T; read(T);
while (T--) {
read(n);
FOR(i, 1, n) read(a[i]);
if (is_sorted(a + 1, a + n + 1)) {
print(0);
continue;
}
if (a[n - 1] > a[n] || a[n] < 0) {
print(-1);
continue;
}
print(n - 2);
FOR(i, 1, n - 2) print(i, n - 1, n);
}
return output(), 0;
}
1635D - Infinite Set
题意:给定一个序列 a 1 ⋯ n a_{1\cdots n} a1⋯n,令集合 S S S 如下构造:
- ∀ i \forall i ∀i, a i ∈ S a_i\in S ai∈S。
- ∀ x ∈ S \forall x\in S ∀x∈S, 2 x + 1 ∈ S 2x +1\in S 2x+1∈S。
- ∀ x ∈ S \forall x\in S ∀x∈S, 4 x ∈ S 4x\in S 4x∈S。
问集合 S S S 中严格小于 2 p 2^p 2p 的元素个数,答案对 1 0 9 + 7 10^9+7 109+7 取模, n , p ≤ 2 × 1 0 5 n,p\le 2\times 10^5 n,p≤2×105。
发现 p p p 很大,且 2 x + 1 2x + 1 2x+1 和 4 x 4x 4x 这样的操作让人联想到位运算。所以先在二进制下考虑这个问题的 n = 1 n = 1 n=1 版本。
发现,每次做插入 2 x + 1 2x+1 2x+1 和插入 4 x 4x 4x 这样构成了一个决策树,得到的答案绝对不会相交。而 2 x + 1 2x+1 2x+1 相当于将 x x x 左移一位再将最后一位设成 1 1 1, 4 x 4x 4x 相当于将 x x x 左移两位。所以我们实际上就是将 x x x 往左移一定的位数,保证最高位 $ < p$ 即可。
统计一下这样的方案数:不难发现设
f
i
f_i
fi 为左移
i
i
i 位能得到的数字个数,有
{
f
1
=
1
f
2
=
2
f
i
=
f
i
−
1
+
f
i
−
2
+
1
i
≥
3
\begin{cases} f_1 = 1\\ f_2 = 2\\ f_i = f_{i-1} + f_{i-2} + 1&i\ge 3 \end{cases}
⎩⎪⎨⎪⎧f1=1f2=2fi=fi−1+fi−2+1i≥3
于是
n
=
1
n=1
n=1 的情况就考虑完了。对于原来那种“从数集”扩展的模式,最麻烦的就是
a
i
a_i
ai 能变换到
a
j
a_j
aj,这样子方案数就会算重。所以考虑去重。发现从决策树的节点往根跳的过程很容易(因为最后一步是确定的),所以对于每个
a
i
a_i
ai,都这样往回跳跳,如果跳到了某个
a
j
a_j
aj,就直接不用考虑
a
i
a_i
ai 即可。
于是本题得到了解决。
const int maxn = 2e5 + 5;
int n, p, a[maxn];
modint f[maxn];
std::set<int> S;
int main() {
read(n, p);
f[p] = 1;
DEC(i, p - 1, 1) f[i] = f[i + 1] + f[i + 2] + 1;
FOR(i, 1, n) read(a[i]), S.insert(a[i]);
modint ans = 0;
DEC(i, n, 1) {
bool flg = 1;
int x = a[i];
while (x) {
if (x & 1) x >>= 1;
else if (x % 4) break;
else x >>= 2;
if (S.count(x)) {
flg = 0;
break;
}
}
if (flg) {
int highbit = 0;
DEC(j, 30, 0) if ((1 << j) & a[i]) {
highbit = j;
break;
}
ans += f[highbit + 1];
}
}
print(ans);
return output(), 0;
}
1635E - Cars
一维数轴上,初始在 n n n 个不同位置有 n n n 辆车,车的朝向确定,而速度可以为任意常数。定义两种车子之间的关系:
- 称两辆车风马牛不相及当且仅当不论他们的速度如何,他们都永远无法相遇。
- 称两辆车双向奔赴命中注定当且仅当不论他们的速度如何,他们都能在一个点相遇。
现在给定 m m m 条这样的关系,请还原出 n n n 辆车的坐标和朝向。 n , m ≤ 2 × 1 0 5 n,m\le 2\times 10^5 n,m≤2×105。
首先不难发现,“风马牛不相及”指的就是两辆车是往两边散开的,“双向奔赴命中注定”指的就是两辆车是往中间双向奔赴的。有关系的车的朝向都是不一样的,这个可以用二分图染色染一下。
然后,对于风马牛不相及的车的关系,可以发现 x L < x R x_L < x_R xL<xR,相应的对于双向奔赴的,有 x R < x L x_R < x_L xR<xL,于是我们可以考虑给原来二分图的边定向:若 x u < x v x_u < x_v xu<xv,则连边 u → v u\to v u→v,反之亦然。
这样下来,发现这些约束关系合法当且仅当图是张 DAG,并且需要求方案的话输出拓扑序就可以了。所以总结一下算法流程:二分图染色(判断无解),给边定向,拓扑排序(判断无解)。
const int maxn = 2e5 + 5;
vector<int> G0[maxn], G[maxn];
int n, m, vis[maxn], col[maxn], ind[maxn], x[maxn];
bool flg = 1;
struct Relation {
int op, u, v;
} a[maxn];
void dfs(int u, int cur) {
col[u] = cur, vis[u] = 1;
for (int &v : G0[u]) {
if (vis[v] && col[v] == cur) flg = 0;
if (vis[v]) continue;
dfs(v, cur == 1 ? 2 : 1);
}
}
int main() {
read(n, m);
FOR(i, 1, m) read(a[i].op, a[i].u, a[i].v), G0[a[i].u].push_back(a[i].v), G0[a[i].v].push_back(a[i].u);
FOR(i, 1, n) if (!vis[i]) dfs(i, 1);
if (!flg) {
print("NO");
} else {
FOR(i, 1, m) {
const int &u = a[i].u, &v = a[i].v;
if (a[i].op == 1) {
if (col[u] == 1) G[u].push_back(v), ++ind[v];
else G[v].push_back(u), ++ind[u];
} else {
if (col[u] == 2) G[u].push_back(v), ++ind[v];
else G[v].push_back(u), ++ind[u];
}
}
queue<int> q;
FOR(i, 1, n) if (!ind[i]) q.push(i);
int cntx = 0;
while (!q.empty()) {
int u = q.front();
x[u] = ++cntx;
q.pop();
for (const int &v : G[u]) {
if (!--ind[v]) q.push(v);
}
}
if (cntx != n) {
print("NO");
} else {
print("YES");
FOR(i, 1, n) {
putchar(col[i] == 1 ? 'L' : 'R'); putchar(' ');
print(x[i]);
}
}
}
return output(), 0;
}
1635F - Closest Pair
n n n 个二元组 ( x i , w i ) (x_i, w_i) (xi,wi), ∣ x i ∣ , w i ≤ 1 0 9 |x_i|,w_i\le 10^9 ∣xi∣,wi≤109, n ≤ 3 × 1 0 5 n\le 3\times 10^5 n≤3×105, x i x_i xi 升序。 q ≤ 3 × 1 0 5 q\le 3\times 10^5 q≤3×105 次询问,给出 [ l , r ] [l,r] [l,r],求
min l ≤ i < j ≤ r ∣ x i − x j ∣ ( w i + w j ) \min_{l\le i < j\le r}|x_i - x_j|(w_i + w_j) l≤i<j≤rmin∣xi−xj∣(wi+wj)
数据结构结论题。
考虑哪些 ( i , j ) (i,j) (i,j) 是不优的。
- 若存在 p p p 和 q q q,使得 x p < x q ∧ w p > w q x_p < x_q\land w_p > w_q xp<xq∧wp>wq,则 q q q 作为 i i i 一定比 p p p 优。
- 若存在 p p p 和 q q q,使得 x p < x q ∧ w p < w q x_p < x_q\land w_p < w_q xp<xq∧wp<wq,则 p p p 作为 j j j 一定比 q q q 优。
于是,令 L i = max { j : j < i ∧ w j ≤ w i } L_i = \max\{j:j < i\land w_j\le w_i\} Li=max{j:j<i∧wj≤wi}, R i = min { j : j > i ∧ w j ≤ w i } R_i = \min\{j:j > i\land w_j\le w_i\} Ri=min{j:j>i∧wj≤wi},答案就只能取形如 [ i , R i ] [i, R_i] [i,Ri] 或 [ L i , i ] [L_i, i] [Li,i] 的区间。这样的区间一共有 2 n 2n 2n 个。
先用单调栈求出所有的 L i L_i Li 和 R i R_i Ri,再计算出这 2 n 2n 2n 个区间 [ l i , r i ] [l_i, r_i] [li,ri] 的价值 c i c_i ci,在每个 r i r_i ri 上挂上标记 ( l i , c i ) (l_i, c_i) (li,ci),在每个询问 ( i , q l , q r ) (i, ql, qr) (i,ql,qr) 的 q r qr qr 上挂上标记 ( q l , i ) (ql, i) (ql,i)。然后扫描线扫右端点,线段树或者树状数组维护一下 RMQ 和单点修改,这题就做完了。
using ll = long long;
using pll = pair<ll, ll>;
const int maxn = 3e5 + 5;
int n, m, L[maxn], R[maxn], stk[maxn], top;
ll t[maxn << 2], ans[maxn];
pll a[maxn];
vector<pll> op[maxn], q[maxn];
il ll calc(int i, int j) {return myabs(a[i].first - a[j].first) * (a[i].second + a[j].second);}
#define L (k << 1)
#define R (L | 1)
#define M ((i + j) >> 1)
void modify(int i, int j, int k, int x, ll v) {
if (i == j) {
chkmin(t[k], v);
return;
}
if (x <= M) modify(i, M, L, x, v);
else modify(M + 1, j, R, x, v);
t[k] = min(t[L], t[R]);
return;
}
ll query(int i, int j, int k, int x, int y) {
if (x <= i && y >= j) return t[k];
ll ret = 2e18;
if (x <= M) chkmin(ret, query(i, M, L, x, y));
if (y > M) chkmin(ret, query(M + 1, j, R, x, y));
return ret;
}
#undef L
#undef R
#undef M
int main() {
read(n, m);
FOR(i, 1, n) read(a[i].first, a[i].second);
FOR(i, 1, m) {
int l, r; read(l, r);
q[r].push_back({l, i});
}
FOR(i, 1, n) {
while (top > 0 && a[stk[top]].second > a[i].second) --top;
L[i] = stk[top], stk[++top] = i;
}
top = 0;
DEC(i, n, 1) {
while (top > 0 && a[stk[top]].second > a[i].second) --top;
R[i] = stk[top], stk[++top] = i;
}
FOR(i, 1, n) {
if (L[i]) op[i].push_back({L[i], calc(L[i], i)});
if (R[i]) op[R[i]].push_back({i, calc(i, R[i])});
}
memset(t, 0x3f, sizeof t);
FOR(i, 1, n) {
for (auto &p : op[i]) modify(1, n, 1, p.first, p.second);
for (auto &p : q[i]) ans[p.second] = query(1, n, 1, p.first, i);
}
FOR(i, 1, m) print(ans[i]);
return output(), 0;
}