文章目录
A. Floor Tiles
Problem
给定两种不同图案的地砖,问怎么拼接成大小为 N ⋅ M N\cdot M N⋅M,且只有 K K K 条连续曲线,同时要求第 x x x 行,第 y y y 列地砖的图案为 t ( t ∈ { ′ A ′ , ′ B ′ } ) t\ (t\in \{'A','B'\}) t (t∈{′A′,′B′})。
数据范围: 1 ≤ N , M ≤ 800 , 1 ≤ K ≤ 2 ⋅ N ⋅ M 1\le N,M\le 800,1\le K\le 2\cdot N\cdot M 1≤N,M≤800,1≤K≤2⋅N⋅M。
Solution
首先可以发现最小的连续曲线的数量是全放同一种图案,此时共有 N + M N+M N+M条曲线;而数量最多的连续曲线可以通过构造 A B B A \begin{matrix} A & B \\ B & A \end{matrix} ABBA 获得内部圆的方式得到。
如果左上角为 A A A,有 N + M + ⌊ ( N − 1 ) ( M − 1 ) + 1 2 ⌋ N+M+\lfloor \frac{(N-1)(M-1)+1}{2}\rfloor N+M+⌊2(N−1)(M−1)+1⌋
如果左上角为 B B B,有 N + M + ⌊ ( N − 1 ) ( M − 1 ) 2 ⌋ N+M+\lfloor \frac{(N-1)(M-1)}{2}\rfloor N+M+⌊2(N−1)(M−1)⌋
因此对小于下界或大于上界的 K K K,直接输出 ′ N o ′ 'No' ′No′,而在范围内的 K K K 可以先通过给定的瓷砖图案构造出最多的连续曲线,再将多余的曲线去掉即可。去掉多余曲线时可以选择用给定的图案覆盖即可。
时间复杂度: O ( N M ) O(NM) O(NM)
Code
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>
#define ll long long
using namespace std;
const int N = 1e5;
int T;
int n, m, k, x, y;
char t, ans[1000][1000];
void solve()
{
cin >> n >> m >> k;
cin >> x >> y >> t;
// printf("%c", t);
int mi = n + m, ma = n + m + ((n - 1) * (m - 1) + 1) / 2;
int p = 0;
if ((t == 'A' && (x + y) % 2 == 1) || (t == 'B' && (x + y) % 2 == 0))
{
p = 1;
ma = n + m + (n - 1) * (m - 1) / 2;
}
if (k > ma || k < mi)
{
printf("No\n");
return;
}
k = ma - k;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if ((i + j) % 2 == p)
{
ans[i][j] = 'A';
}
else
{
ans[i][j] = 'B';
}
}
}
for (int i = 1; i < n; i++)
{
for (int j = 1; j < m; j++)
{
if (ans[i][j] == 'A' && ans[i][j + 1] == 'B' && k > 0)
{
ans[i][j] = t;
ans[i][j + 1] = t;
k--;
}
}
}
printf("Yes\n");
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
printf("%c", ans[i][j]);
}
printf("\n");
}
}
int main()
{
cin >> T;
while (T--)
{
solve();
}
return 0;
}
B. MST
Problem
给定 n n n 个顶点和 m m m 条边,以及 q q q 次询问,每次询问给定 k k k 个不同的顶点,问由这 k k k 个顶点构成的子图的最小生成树的边权之和是多少。
数据范围: 2 ≤ n ≤ 1 0 5 , 1 ≤ m , q ≤ 1 0 5 , ∑ k ≤ 1 0 5 2\le n\le 10^5,1\le m,q\le 10^5,\sum k \le 10^5 2≤n≤105,1≤m,q≤105,∑k≤105
Solution
该问题主要是需要解决快速找到子图的边,如果每次遍历所有边,复杂度为 O ( q m ) O(qm) O(qm),如果每次遍历边的两个顶点,复杂度为 O ( ∑ k 2 ) O(\sum k^2) O(∑k2),这两种方法显然不行,不过将这两种方法结合起来就可以解决该问题。
对 k > n k > \sqrt{n} k>n 的情况,通过遍历所有边找到子图的边,复杂度 O ( m n ) O(m\sqrt{n}) O(mn);对 k ≤ n k\le \sqrt{n} k≤n 的情况,通过遍历两个顶点的找到子图的边,复杂度为 O ( n n ) O(n\sqrt{n}) O(nn)。
将得到的子图的边用 k r u s k a l kruskal kruskal 算法求最小生成树即可。 k r u s k a l kruskal kruskal 算法求最小生成树的复杂度为 O ( m l o g m ) O(m\ logm) O(m logm)
Code
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>
#define ll long long
using namespace std;
const int N = 1e5;
int n, m, q, k;
int f[N + 5], a[N + 5];
struct edge
{
int u, v;
ll w;
edge(int a, int b, ll c)
{
u = a, v = b, w = c;
}
};
int find(int x)
{
return x == f[x] ? x : f[x] = find(f[x]);
}
bool merge(int x, int y)
{
x = find(x);
y = find(y);
if (x == y)
{
return false;
}
f[y] = x;
return true;
}
bool cmp(edge x, edge y)
{
return x.w < y.w;
}
int main()
{
cin >> n >> m >> q;
int lim = sqrt(n);
vector<edge> G;
map<pair<int, int>, ll> mp;
for (int i = 0; i < m; i++)
{
int u, v;
ll w;
cin >> u >> v >> w;
if (u < v)
{
swap(u, v);
}
G.push_back(edge(u, v, w));
mp[{u, v}] = w;
}
for (int i = 1; i <= q; i++)
{
cin >> k;
for (int j = 1; j <= k; j++)
{
cin >> a[j];
f[a[j]] = a[j];
}
int cnt = 0;
ll ans = 0;
vector<edge> Q;
if (k <= lim)
{
for (int x = 1; x <= k; x++)
{
for (int y = x + 1; y <= k; y++)
{
int u = a[x], v = a[y];
if (u < v)
{
swap(u, v);
}
if (mp.count({u, v}))
{
Q.push_back(edge(u, v, mp[{u, v}]));
}
}
}
}
else
{
for (int j = 0; j < m; j++)
{
int u = G[j].u, v = G[j].v;
ll w = G[j].w;
if (f[u] && f[v])
{
Q.push_back(edge(u, v, w));
}
}
}
sort(Q.begin(), Q.end(), cmp);
for (int i = 0; i < Q.size(); i++)
{
int u = Q[i].u, v = Q[i].v;
if (merge(u, v))
{
ans += mp[{u, v}];
cnt++;
}
}
for (int j = 1; j <= k; j++)
{
f[a[j]] = 0;
}
if (cnt == k - 1)
{
printf("%lld\n", ans);
}
else
{
printf("-1\n");
}
}
return 0;
}
C. Red Walking on Grid
Problem
给定一个 2 × n 2\times n 2×n的网格,网格上要么为红色要么为白色,要求只能在红色网格上下左右移动,同时每个位置不能重复经过,问最多能走多少步。
数据范围: 1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1≤n≤106
Solution
将每行连续的颜色为红色的网格作为区间提取出来,并按左端点排序。对于两个区间 [ l i , r i ] , [ l j , r j ] [l_i,r_i],[l_j,r_j] [li,ri],[lj,rj],只有两种情况需要特殊考虑。
当 l i < l j ≤ r j < r i l_i<l_j\le r_j<r_i li<lj≤rj<ri 时,即两个区间包含,并且 r j − l j + 1 r_j-l_j+1 rj−lj+1 为奇数时,答案要减一。
当 l i < l j < r i < r j l_i<l_j<r_i <r_j li<lj<ri<rj时,即两个区间相交,并且 r i − l j + 1 r_i-l_j+1 ri−lj+1 为偶数时,答案要减一。
对于其他情况一律加上区间长度即可。
时间复杂度: O ( n l o g n ) O(n\ logn) O(n logn)
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e3;
const ll M = 1000000007;
int n;
string s[2];
int main()
{
cin >> n;
cin >> s[0] >> s[1];
s[0] = s[0] + 'W';
s[1] = s[1] + 'W';
vector<pair<int, int>> p;
for (int j = 0; j < 2; j++)
{
int len = 0, l = 0;
for (int i = 0; i <= n; i++)
{
if (s[j][i] == 'R')
{
len++;
}
else
{
if (len > 0)
{
p.push_back({l, l + len - 1});
}
len = 0;
l = i + 1;
}
}
}
sort(p.begin(), p.end());
int ans = 0, cur = 0, ma = -1, pre = -1;
for (int i = 0; i < p.size(); i++)
{
// printf("%d %d\n", p[i].first, p[i].second);
if (p[i].first > ma)
{
ans = max(ans, cur);
cur = p[i].second - p[i].first + 1;
}
else
{
cur += p[i].second - p[i].first + 1;
if (p[i].second < ma && (p[i].second - p[i].first + 1) % 2 == 1 && p[i].first != pre)
{
cur--;
}
if (p[i].second > ma && (ma - p[i].first + 1) % 2 == 0 && p[i].first != pre)
{
cur--;
}
}
ma = max(ma, p[i].second);
pre = p[i].first;
}
ans = max(ans, cur);
printf("%d\n", max(0, ans - 1));
return 0;
}
E. GCD VS XOR
Problem
给定整数 x x x,要求找到一个整数 y ( 0 < y < x ) y\ (0<y<x) y (0<y<x),使得 g c d ( x , y ) = x ⊕ y gcd(x,y)=x\oplus y gcd(x,y)=x⊕y。
数据范围: 1 ≤ x ≤ 1 0 18 1\le x\le 10^{18} 1≤x≤1018
Solution
当 x x x 为奇数时, y = x − 1 y=x-1 y=x−1 显然能符合条件,当 x x x 为偶数时,将 x x x 表示成二进制形式后,发现将最后一个为 1 1 1 的位减去后也可以满足条件。
因此答案即为 x − l o w b i t ( x ) x-lowbit(x) x−lowbit(x),其中为 l o w b i t ( x ) = x & − x lowbit(x)=x\ \&-x lowbit(x)=x &−x。
当 l o w b i t ( x ) = x lowbit(x)=x lowbit(x)=x 时,不存在这样的非负整数,输出 − 1 -1 −1 即可。
时间复杂度: O ( 1 ) O(1) O(1)
Code
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <cmath>
#define ll long long
using namespace std;
const int N = 1e5;
int T;
ll lowbit(ll x)
{
return x & -x;
}
void solve()
{
ll x;
cin >> x;
ll y = x - lowbit(x);
if (y == 0)
{
printf("-1\n");
}
else
{
printf("%lld\n", y);
}
}
int main()
{
cin >> T;
while (T--)
{
solve();
}
return 0;
}
H. Instructions Substring
Problem
给定一个长度为 n n n 的字符串,只包含 W 、 S 、 A 、 D W、S、A、D W、S、A、D 这四个大写字母,表示向上下左右移动一个单位,初始时位于 ( 0 , 0 ) (0,0) (0,0),问有多少个连续子串能够经过 ( x , y ) (x,y) (x,y)。
数据范围: 1 ≤ n ≤ 2 × 1 0 5 , − 1 0 5 ≤ x , y ≤ 1 0 5 1\le n\le 2\times10^5,-10^5\le x,y\le 10^5 1≤n≤2×105,−105≤x,y≤105。
Solution
因为要求的是能够经过 ( x , y ) (x,y) (x,y),假如子串区间为 [ l , r ] [l,r] [l,r] 恰好能到达点 ( x , y ) (x,y) (x,y),那么所有左端点为 l l l,右端点大于 r r r 的子串都是符合条件的。
因此可以通过从后往前遍历解决,对于当前的左端点 i i i,找到恰好到达 ( x , y ) (x,y) (x,y) 的右端点 j j j, a n s = a n s + n − j + 1 ans=ans+n-j+1 ans=ans+n−j+1。
时间复杂度: O ( n l o g n ) O(n\ logn) O(n logn)
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6;
const ll M = 1000000007;
int n, x, y;
string s;
int main()
{
cin >> n >> x >> y;
cin >> s;
if (x == 0 && y == 0)
{
printf("%lld\n", (ll)n * (n + 1) / 2);
return 0;
}
map<pair<int, int>, ll> mp;
mp[{0, 0}] = n;
int nx = 0, ny = 0;
ll ans = 0;
for (int i = n - 1; i >= 0; i--)
{
if (s[i] == 'W')
{
ny++;
}
else if (s[i] == 'A')
{
nx--;
}
else if (s[i] == 'S')
{
ny--;
}
else
{
nx++;
}
//printf("%d %d\n", nx, ny);
if (mp.count({nx - x, ny - y}))
{
ans += n - mp[{nx - x, ny - y}] + 1;
}
mp[{nx, ny}] = i;
}
printf("%lld\n", ans);
return 0;
}
I. Red Playing Cards
Problem
给定长度为 2 ⋅ n 2\cdot n 2⋅n 的数组 { a } i = 1 2 n \{a\}_{i=1}^{2n} {a}i=12n,其中 1 ∼ n 1\sim n 1∼n 每个元素恰好出现两次。每次可以选择一个整数 i i i,找到数组中等于 i i i 的位置,记为 l i , r i l_i,r_i li,ri,然后移除区间 [ l i , r i ] [l_i,r_i] [li,ri] 范围内的所有元素,同时得分增加 i × ( r i − l i + 1 ) i\times(r_i-l_i+1) i×(ri−li+1),问最大可以获得多少。
数据范围: 1 ≤ n ≤ 3000 1\le n\le 3000 1≤n≤3000
Solution
记 f i f_i fi 表示数 i i i 的区间 [ l i , r i ] [l_i,r_i] [li,ri] 可以获得的最大分数,如果区间内的数都比 i i i 小,那么直接将这个区间移除是最优的,如果区间内存在一对数 j ( j > i ) j(j>i) j(j>i),那么应该先移除 [ l j , r j ] [l_j,r_j] [lj,rj],再移除 [ l i , r i ] [l_i,r_i] [li,ri],是更优的策略。
因此可以通过 d p dp dp 来求解 f i f_i fi,记 g k g_k gk 表示区间 [ l i , k ] [l_i,k] [li,k] 的最大分数,状态转移方程如下:
如果 k = r j k=r_j k=rj,并且 l j > l i l_j>l_i lj>li, g k = m a x ( g k − 1 + i , g l j − 1 + f j ) g_k=max(g_{k-1}+i,g_{l_j-1}+f_j) gk=max(gk−1+i,glj−1+fj)。
其他情况下, g k = g k − 1 + i g_k=g_{k-1}+i gk=gk−1+i。
因为只有比数 i i i 大的数才会对 f i f_i fi 有影响的,因此从大到小求解 f i f_i fi 即可,可以在数组两端加上值为 0 0 0 的元素,那么最后的答案即为 f 0 f_0 f0。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6;
const ll M = 1000000007;
int n, a[N + 5], f[N + 5], g[N + 5], l[N], r[N];
int main()
{
cin >> n;
for (int i = 1; i <= 2 * n; i++)
{
cin >> a[i];
r[a[i]] = i;
}
for (int i = 2 * n; i >= 1; i--)
{
l[a[i]] = i;
}
a[0] = 0, a[2 * n + 1] = 0;
l[0] = 0, r[0] = 2 * n + 1;
for (int i = n; i >= 0; i--)
{
for (int i = 0; i <= 2 * n + 1; i++)
{
g[i] = 0;
}
for (int j = l[i] + 1; j < r[i]; j++)
{
if (j == r[a[j]] && l[a[j]] > l[i])
{
g[j] = max(g[j - 1] + i, g[l[a[j]] - 1] + f[a[j]]);
}
else
{
g[j] = g[j - 1] + i;
}
}
f[i] = g[r[i] - 1] + 2 * i;
// printf("%d\n", f[i]);
}
printf("%d\n", f[0]);
return 0;
}