唠嗑
这一场题目基本比较模板,所以就稍微写一下题解吧
题解
A. Dice Rolling
有一个色子,六个面为 2 2 2到 7 7 7,每一次给定一个数 x x x,问可以掷多少次色子使得最上面的数的和为 x x x。
解法
- 可以发现,尽量用 7 7 7就可以了,答案显然为 ⌈ x 7 ⌉ \lceil\frac{x}{7}\rceil ⌈7x⌉。
代码
#include <bits/stdc++.h>
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x < y ? x : y;}
template <typename T> void read(T &x) {
x = 0; int f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
int main() {
int T; read(T);
while (T--) {
int x; read(x);
int y = x % 7, z = x / 7;
if (y == 0) {cout << z << "\n"; continue;}
cout << z + 1 << "\n";
}
return 0;
}
B. Letters Rearranging
每一次给定一个字符串 s s s,问能否构造一个顺序使得最后的字符串不是回文串。
解法
- 直接sort即可
代码
#include <bits/stdc++.h>
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x < y ? x : y;}
template <typename T> void read(T &x) {
x = 0; int f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
int s[100];
int main() {
int T; cin >> T;
while (T--) {
string st; cin >> st;
memset(s, 0, sizeof(s));
int l = st.size(), mx = 0;
for (int i = 0; i < l; i++) s[st[i] - 'a']++, chkmax(mx, s[st[i] - 'a']);
if (mx == l) {cout << "-1\n"; continue;}
for (int i = 0; i < 26; i++)
for (int j = 1; j <= s[i]; j++)
putchar(i + 'a');
cout << endl;
}
return 0;
}
C. Mishka and the Last Exam
有一个长度为
n
n
n的序列
a
a
a,告诉你长度为
n
2
\frac{n}{2}
2n的序列
b
b
b满足
b
[
i
]
=
a
[
i
]
+
a
[
n
−
i
+
1
]
b[i]=a[i]+a[n-i+1]
b[i]=a[i]+a[n−i+1]。问是否存在单调不降的序列
a
a
a,如果有输出方案。
n
≤
2
×
1
0
5
n≤2×10^5
n≤2×105
解法
- 显然,我们肯定让 a [ 1 ] = 0 , a [ n ] = b [ n / 2 ] a[1]=0,a[n]=b[n/2] a[1]=0,a[n]=b[n/2]
- 然后为了使尽量单调,所以我们让 a [ i ] a[i] a[i]尽量小就可以了。
- 时间复杂度: O ( n ) O(n) O(n)。
代码
#include <bits/stdc++.h>
#define int long long
#define N 300010
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x < y ? x : y;}
template <typename T> void read(T &x) {
x = 0; int f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
int a[N], b[N];
signed main() {
int n; read(n);
for (int i = 1; i <= n / 2; i++) read(b[i]);
a[1] = 0, a[n] = b[1];
for (int i = 2; i <= n / 2; i++) {
int x = LONG_LONG_MAX;
if (b[i] - a[i - 1] <= a[n - i + 2]) x = a[i - 1];
if (b[i] >= a[n - i + 2] && b[i] - a[n - i + 2] >= a[i - 1]) chkmin(x, b[i] - a[n - i + 2]);
a[i] = x, a[n - i + 1] = b[i] - a[i];
}
for (int i = 1; i <= n; i++) cout << a[i] << ' '; cout << "\n";
return 0;
}
D. Beautiful Graph
给定一个 n n n个点, m m m条边的无向图,每一个点的点权可以为 1 , 2 , 3 1,2,3 1,2,3,相邻的点权之和不能为偶数,问有多少种点权赋值方案。
解法
- 对于每一个联通块单独考虑,答案显然为所有联通块答案之积。
- 显然可以发现,相邻两个点的点权奇偶性不同,那么就可以变成一个二分图。假设白点有 x x x个,黑点有 y y y个,那么答案就为 2 x + 2 y 2^x+2^y 2x+2y。
- 时间复杂度: O ( m ) O(m) O(m)
代码
#include <bits/stdc++.h>
#define Mod 998244353
#define N 300010
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x < y ? x : y;}
template <typename T> void read(T &x) {
x = 0; int f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Edge {int next, num;} e[N * 10];
int s, cnt, tot, flag, vis[N], col[N];
void add(int x, int y) {
e[++cnt] = (Edge) {e[x].next, y};
e[x].next = cnt;
}
void dfs(int x, int c) {
if (vis[x]) {if (col[x] != c) flag = false; return;}
vis[x] = 1, col[x] = c, tot++; if (c) s++;
for (int p = e[x].next; p; p = e[p].next) dfs(e[p].num, c ^ 1);
}
int Pow(int x, int y) {
int ret = 1;
while (y) {
if (y & 1) ret = 1ll * ret * x % Mod;
y >>= 1, x = 1ll * x * x % Mod;
}
return ret;
}
int main() {
int T; read(T);
while (T--) {
int n, m; read(n), read(m); cnt = n;
for (int i = 1; i <= n; i++) e[i].next = 0, vis[i] = 0, col[i] = 0;
for (int i = 1; i <= m; i++) {
int x, y; read(x), read(y);
add(x, y), add(y, x);
}
int ans = 1;
for (int i = 1; i <= n; i++)
if (!vis[i]) {
flag = true, s = tot = 0; dfs(i, 0);
if (!flag) {ans = 0; continue;}
ans = 1ll * ans * ((Pow(2, s) + Pow(2, tot - s)) % Mod) % Mod;
}
cout << ans << "\n";
}
return 0;
}
E. Intersection of Permutations
给定
1
−
n
1-n
1−n的排列
a
a
a和
b
b
b,有
q
q
q组操作:1.交换
b
[
x
]
,
b
[
y
]
b[x],b[y]
b[x],b[y] 2.询问
a
[
l
]
,
…
,
a
[
r
]
a[l],\dots,a[r]
a[l],…,a[r]的数集与
b
[
L
]
,
…
,
b
[
R
]
b[L],\dots,b[R]
b[L],…,b[R]的数集的交集的大小。
n
,
q
≤
2
×
1
0
5
n,q\leq 2\times 10^5
n,q≤2×105
解法
- 考虑将 b b b中的元素变成在 a a a中出现的位置,那么就变成每一次询问 b b b中的一段区间 [ L , R ] [L,R] [L,R]中的元素大小在 [ l , r ] [l,r] [l,r]的有多少个。
- 显然可以使用树套树实现,但是可能会出现空间不够的情况。
- 那么不妨考虑cdq分治,不过要将操作拆成多个。
- 时间复杂度: O ( q log 2 n ) O(q\log^2 n) O(qlog2n)
代码
#include <bits/stdc++.h>
#define N 200010
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x < y ? x : y;}
template <typename T> void read(T &x) {
x = 0; int f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Node {
int x, l, r, t, id;
Node() {}
Node(int _x, int _l, int _r, int _t, int _id) {x = _x; l = _l; r = _r; t = _t; id = _id;}
} q[N * 10], cur[N * 10];
int n, c[N], a[N], b[N], posa[N], posb[N], ans[N];
void add(int x, int v) {for (; x <= n; x += x & -x) c[x] += v;}
int query(int x) {int ans = 0; for(; x; x -= x & -x) ans += c[x]; return ans;}
void cdq(int l, int r) {
if (l >= r) return;
int mid = l + r >> 1;
cdq(l, mid), cdq(mid + 1, r);
int v = l, j = l, k = mid + 1;
while (j <= mid && k <= r) {
if (q[j].x <= q[k].x) {
if (q[j].id == 0) add(q[j].l, q[j].t);
cur[v++] = q[j++];
} else {
if (q[k].id != 0) ans[q[k].id] += q[k].t * (query(q[k].r) - query(q[k].l - 1));
cur[v++] = q[k++];
}
}
while (j <= mid) {
if (q[j].id == 0) add(q[j].l, q[j].t);
cur[v++] = q[j++];
}
while (k <= r) {
if(q[k].id != 0) ans[q[k].id] += q[k].t * (query(q[k].r) - query(q[k].l - 1));
cur[v++] = q[k++];
}
for (int i = l; i <= mid; i++) if(q[i].id == 0) add(q[i].l, -q[i].t);
for (int i = l; i <= r; i++) q[i] = cur[i];
}
int main() {
int m; read(n), read(m);
for (int i = 1; i <= n; i++) read(a[i]), posa[a[i]] = i;
for (int i = 1; i <= n; i++) read(b[i]), posb[b[i]] = i;
int tot = 0, cnt = 0;;
for (int i = 1; i <= n; i++) q[++tot] = Node(posb[a[i]], i, 0, 1, 0);
for (int i = 1; i <= m; i++) {
int op, A, B, C, D; read(op), read(A), read(B);
if (op == 1) {
read(C), read(D);
q[++tot] = Node(C - 1, A, B, -1, ++cnt);
q[++tot] = Node(D, A, B, 1, cnt);
} else {
q[++tot] = Node(A, posa[b[A]], 0, -1, 0);
q[++tot] = Node(B, posa[b[B]], 0, -1, 0);
swap(b[A], b[B]);
q[++tot] = Node(A, posa[b[A]], 0, 1, 0);
q[++tot] = Node(B, posa[b[B]], 0, 1, 0);
}
}
cdq(1, tot);
for (int i = 1; i <= cnt; i++) cout << ans[i] << "\n";
return 0;
}
F. Vasya and Array
给定一个长度为
n
n
n的序列,有些位置没有填入数字。有数字的位置数字大小在
[
1
,
k
]
[1,k]
[1,k]。现在要将没有数字的位置填入
[
1
,
k
]
[1,k]
[1,k]中的任意一个,满足不存在一个连续相同颜色的区间长度不小于
l
e
n
len
len。问有多少种方案。
l
e
n
≤
n
≤
1
0
5
,
k
≤
100
len\leq n\leq 10^5,k\leq 100
len≤n≤105,k≤100
解法
- 考虑这样一个dp:设 f [ i ] [ j ] f[i][j] f[i][j]表示前 i − 1 i-1 i−1位已经填好并且合法,且第 i i i位的元素为 j j j的方案数。
- 同时,我们定义 s [ i ] = ∑ j = 1 k f [ i ] [ j ] s[i]=\sum_{j=1}^k f[i][j] s[i]=∑j=1kf[i][j]。
- 那么,如果我们不考虑合不合法的情况,那么 f [ i ] [ j ] = s [ i − 1 ] ( a [ i ] = − 1 ∣ ∣ a [ i ] = j ) f[i][j]=s[i-1]\ \ (a[i]=-1||a[i]=j) f[i][j]=s[i−1] (a[i]=−1∣∣a[i]=j)。
- 然后考虑如何去除不合法的情况,如果 [ i − l e n + 1 , i ] [i-len+1,i] [i−len+1,i]这段区间都可以变成 j j j这个元素,那么不合法的序列的数量就是 s [ i − l e n ] − f [ i − l e n ] [ j ] s[i-len]-f[i-len][j] s[i−len]−f[i−len][j]。
- 时间复杂度: O ( n k ) O(nk) O(nk)。
代码
#include <bits/stdc++.h>
#define Mod 998244353
#define N 100010
using namespace std;
template <typename T> void chkmin(T &x, T y) {x = x < y ? x : y;}
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void read(T &x) {
x = 0; int f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
int a[N], s[N], f[N][105], b[N][105];
void add(int &x, int y) {x += y; if (x >= Mod) x -= Mod;}
int main() {
int n, k, len; read(n), read(k), read(len);
if (len == 1) return cout << "0\n", 0; s[0] = 1;
for (int i = 1; i <= n; i++) read(a[i]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= k; j++)
if (a[i] == j || a[i] == -1) b[i][j] = 1;
for (int j = 1; j <= k; j++)
for (int i = 1; i <= n; i++)
b[i][j] += b[i - 1][j];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= k; j++) {
if (~a[i] && a[i] != j) continue;
f[i][j] = s[i - 1];
if (i >= len && b[i][j] - b[i - len][j] == len)
add(f[i][j], (f[i - len][j] - s[i - len] + Mod) % Mod);
add(s[i], f[i][j]);
}
}
cout << s[n] << "\n";
return 0;
}
G. Multidimensional Queries
k
k
k维空间给定
n
n
n个点,然后有
q
q
q组操作:1.修改某一个点的坐标 2.询问编号在区间
[
l
,
r
]
[l,r]
[l,r]的点曼哈顿距离最大是多少。
n
,
q
≤
2
×
1
0
5
,
k
≤
5
n,q≤2×10^5,k≤5
n,q≤2×105,k≤5
解法
- 假设现在两个点编号为 i , j i,j i,j,那么这两个点的曼哈顿距离即为 ∑ p = 1 k ∣ x i , p − x j , p ∣ \sum_{p=1}^k |x_{i,p}-x_{j,p}| ∑p=1k∣xi,p−xj,p∣
- 然后我们可以考虑怎么除去绝对值,令 c i = 1 / − 1 c_i=1/-1 ci=1/−1表示绝对值中的正负性,那么式子就可以变成 ∑ p = 1 k c p x i , p − ∑ p = 1 k c p x j , p \sum_{p=1}^k c_px_{i,p}-\sum_{p=1}^k c_px_{j,p} ∑p=1kcpxi,p−∑p=1kcpxj,p
- 可以发现,我们只要对减号两边分别维护最大值和最小值即可。对于 2 k 2^k 2k种状态的每一种都要维护。
- 但是可能会存在一种情况:尽管在这种状态下取到最大值,但是与实际并不符合。
- 那么我们考虑一下为什么这个是正确的。因为存在不符合的项,那么即为拆完了之后那一项是原来的相反数,一定不会比正确的更优。如果它就是最优解,显然在正确的系数下答案会更大,与之前的假设产生矛盾。所以正确性是有保证的。
- 时间复杂度: O ( ( n + q ) 2 k log n ) O((n+q)2^k\log n) O((n+q)2klogn)
代码
#include <bits/stdc++.h>
#define mp make_pair
#define N 800010
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x < y ? x : y;}
template <typename T> void read(T &x) {
x = 0; int f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
typedef pair <int, int> PI;
int K, a[N][5];
struct SegmentTree {
int mx[N][32], mn[N][32];
#define lc k << 1
#define rc k << 1 | 1
void update(int k) {
for (int i = 0; i < (1 << K); i++)
mx[k][i] = max(mx[lc][i], mx[rc][i]),
mn[k][i] = min(mn[lc][i], mn[rc][i]);
}
void build(int k, int l, int r) {
if (l == r) {
for (int i = 0; i < (1 << K); i++)
for (int j = 0; j < K; j++)
if ((i >> j) & 1) mx[k][i] += a[l][j], mn[k][i] += a[l][j];
else mx[k][i] -= a[l][j], mn[k][i] -= a[l][j];
return;
}
int mid = (l + r) >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
update(k);
}
void modify(int k, int l, int r, int x, int a[5]) {
if (l == r) {
for (int i = 0; i < (1 << K); i++) {
mx[k][i] = mn[k][i] = 0;
for (int j = 0; j < K; j++)
if ((i >> j) & 1) mx[k][i] += a[j], mn[k][i] += a[j];
else mx[k][i] -= a[j], mn[k][i] -= a[j];
}
return;
}
int mid = (l + r) >> 1;
if (x <= mid) modify(lc, l, mid, x, a); else modify(rc, mid + 1, r, x, a);
update(k);
}
PI query(int k, int l, int r, int L, int R, int num) {
if (L <= l && r <= R) return mp(mx[k][num], mn[k][num]);
int mid = (l + r) >> 1; PI ret = mp(-INT_MAX, INT_MAX);
if (L <= mid) {
PI t = query(lc, l, mid, L, R, num);
chkmax(ret.first, t.first), chkmin(ret.second, t.second);
}
if (R > mid) {
PI t = query(rc, mid + 1, r, L, R, num);
chkmax(ret.first, t.first), chkmin(ret.second, t.second);
}
return ret;
}
} T;
int main() {
int n; read(n), read(K);
for (int i = 1; i <= n; i++)
for (int j = 0; j < K; j++)
read(a[i][j]);
T.build(1, 1, n);
int m; read(m);
while (m--) {
int op; read(op);
if (op == 2) {
int l, r, ret = 0; read(l), read(r);
for (int i = 0; i < (1 << K); i++) {
pair <int, int> t = T.query(1, 1, n, l, r, i);
chkmax(ret, t.first - t.second);
}
cout << ret << "\n";
} else {
int pos, b[5]; read(pos);
for (int i = 0; i < K; i++) read(b[i]);
T.modify(1, 1, n, pos, b);
}
}
return 0;
}