A.We Got Everything Covered!(贪心)
题意:
给你两个正整数 n n n和 k k k。
找出一个字符串 s s s,使得所有长度为 n n n的字符串都可以用前 k k k个小写英文字母作为 s s s的子序列出现。
如果有多个答案,则输出长度最小的答案。如果仍有多个答案,输出其中任意一个。
注:如果从 b b b中删除一些字符(可能为零个)而不改变其余字符的顺序得到 a a a,则字符串 a a a称为字符串 b b b的子串。
分析:
本题解法不唯一,观察样例可以发现,我们只需要顺倒交替输出前 k k k个字母共 n n n次即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
int t;
cin >> t;
while (t--) {
int n, k;
cin >> n >> k;
string s;
for (int i = 0; i < n; i++) {
for (int j = 0; j < k; j++) {
s += 'a' + j;
}
}
cout << s << endl;
}
return 0;
}
B.A Balanced Problemset?(枚举)
题意:
杰伊成功地创造了一个难度为 x x x的问题。
但Yash担心这个问题会让比赛变得非常不平衡,协调员会拒绝接受它。因此,他决定把它分解成一个由 n n n个子问题组成的问题集,使得所有子问题的难度都是一个正整数,并且它们的总和等于 x x x。
协调人阿列克谢将问题集的平衡定义为问题集中所有子问题难度的 G C D GCD GCD。
求如果Yash最佳地选择子问题的难度,他能达到的最大平衡。
分析:
将 x x x分成 n n n个数,要求在答案尽量大的同时使得所有子问题的难度可以整除答案,那么这个答案就是每个难度的因子,求他们最大的 g c d gcd gcd,显然 g c d gcd gcd肯定是 x x x的因数,且满足 g c d × n ≤ x gcd \times n \le x gcd×n≤x,根号枚举 x x x的因数即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
int t;
cin >> t;
while (t--) {
LL x, n;
cin >> x >> n;
LL tmp = 1;
for (LL i = 1; i <= sqrt(x); i++) {
if (x % i == 0) {
LL cnt = x / i;
if (n * i <= x)
tmp = max(tmp, i);
if (n * cnt <= x)
tmp = max(tmp, cnt);
}
}
cout << tmp << endl;
}
return 0;
}
C.Did We Get Everything Covered?(贪心)(Div1-A)
题意:
给你两个整数 n n n和 k k k以及一个字符串 s s s。
检查是否所有长度为 n n n的字符串都可以用前 k k k个小写英文字母组成,并作为 s s s的子序列出现。如果答案是否定的,还需要输出一个长度为 n n n的字符串,该字符串可以用前 k k k个小写英文字母组成,但不会作为 s s s的子序列出现。
如果有多个答案,输出任意一个。
注:如果从 b b b中删除一些字符(可能为零)而不改变其余字符的顺序,就可以得到 a a a,那么字符串 a a a就被称为字符串 b b b的子串。
分析:
本题与 A A A题类似,我们考虑 Y E S YES YES的情形,即 s s s一定可以分成 n n n份,每份包含所有前 k k k个字符,对字符串从头开始遍历,只要 k k k个字母都凑齐就作为一组,最后如果组数大于等于 n n n,那么 Y E S YES YES,否则 N O NO NO,构造反例时,选取每组的最后一个字母加上最后一组没出现的字母
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int t = 1;
cin >> t;
while (t--) {
int n, k, m;
cin >> n >> k >> m;
string s;
cin >> s;
int num = 0;
int alphanum = 0;
vector<bool> vis(26);
string ans;
for (auto c: s) {
int tmp = c - 'a';
if (tmp < k && !vis[tmp]) {
vis[tmp] = true;
alphanum++;
if (alphanum == k) {
ans += c;
for (int i = 0; i < 26; i++) {
vis[i] = false;
}
alphanum = 0;
num++;
}
}
}
if (num < n) {
cout << "NO" << endl;
char c;
for (int i = 0; i < k; i++) {
if (!vis[i]) {
c = i + 'a';
}
}
while (ans.size() < n) {
ans += c;
}
cout << ans << endl;
} else {
cout << "YES" << endl;
}
}
return 0;
}
D.Good Trip(期望)
题意:
一个班有 n n n个孩子,其中 m m m对是朋友。第 i i i对朋友的友谊值为 f i f_i fi 。
老师要去远足 k k k次,每次远足她都会随机、等价、独立地选择一对孩子。如果选择了一对是朋友的孩子,他们的友谊值在以后的所有远足中都会增加 1 1 1(教师可以多次选择一对孩子)。如果一对孩子不是朋友,那么他们的友谊值为 0 0 0,且在以后的游览中不会改变。
求所有被选中参加远足的 k k k对朋友的友谊值总和的期望值(在被选中时)。可以证明,这个答案总是可以用分数 p q \dfrac{p}{q} qp表示,其中 p p p和 q q q是共整数。计算 p ⋅ q − 1 m o d ( 1 0 9 + 7 ) p\cdot q^{-1} \bmod (10^9+7) p⋅q−1mod(109+7)。
分析:
首先,我们每次出行要从n个人中选取两个人,即 C n 2 C_{n}^{2} Cn2。
那么,每次选择到我们固定要的一对二人组的概率 p = 1 C n 2 p=\frac{1}{C_{n}^{2}} p=Cn21,选不到我们要的二人组的概率为 q = 1 − p q=1-p q=1−p。
有 k k k次远足,即有 k k k个二人组,从中共选到某一个特定二人组 i i i次的概率为 C n 2 × p i × q k − i C_{n}^{2} \times p^i \times q^{k-i} Cn2×pi×qk−i。
如果这个特定二人组不是朋友,那么他们的期望为 0 0 0
如果这个特定二人组是朋友,且初始友谊值为 f i f_i fi,那么他们的期望为 ∑ j = 0 i − 1 ( f a + j ) × C k i × p i × q k − i \sum\limits_{j=0}^{i-1}(f_a+j)\times C_{k}^{i}\times p^i \times q^{k-i} j=0∑i−1(fa+j)×Cki×pi×qk−i。即每选到一次后,友谊值都会加 1 1 1.
题目要求的是所有情况的期望,也就是每个情况的期望的和。
由上述分析可知,不是朋友的二人组期望贡献为 0 0 0,所以我们只需要将朋友二人组的期望加起来即可。
有 m m m个朋友二人组,将他们累加: C k i × p i × q k − i × ∑ a = 1 m ∑ j = 0 i − 1 ( f a + j ) C_{k}^{i}\times p^i \times q^{k-i}\times \sum\limits_{a=1}^{m} \sum\limits_{j=0}^{i-1}(f_a+j) Cki×pi×qk−i×a=1∑mj=0∑i−1(fa+j),化简后发现是一个等差数列,进行高斯求和即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
const LL N = 2e5 + 5;
LL f[N], tmp[N];
LL fix(LL a, LL b) {
LL res = 1;
while (b) {
if (b & 1) {
res = res * a % mod;
}
b >>= 1;
a = a * a % mod;
}
return res;
}
void init(int n) {
f[0] = 1;
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] * i % mod;
}
tmp[n] = fix(f[n], mod - 2);
for (int i = n - 1; i; i--) {
tmp[i] = (i + 1) * tmp[i + 1] % mod;
}
}
int n, m, k;
LL C(LL a, LL b) {
if (a == b || b == 0) {
return 1;
}
return (f[a] * tmp[a - b] % mod) * tmp[b] % mod;
}
LL S(LL l, LL cnt) {
return ((l + (l + (cnt - 1) * m)) * cnt % mod) * tmp[2] % mod;
}
void solve() {
cin >> n >> m >> k;
LL sum = 0;
for (int i = 1; i <= m; i++) {
int a, b, c;
cin >> a >> b >> c;
sum = (sum + c) % mod;
}
LL ans = 0;
LL p = fix(C(n, 2), mod - 2) % mod;
LL q = (((1 - p) % mod) + mod) % mod;
for (int i = 0; i <= k; i++) {
ans = (ans + ((S(sum, i) * (C(k, i) * fix(p, i) % mod) % mod) * fix(q, k - i) % mod)) % mod;
}
cout << ans << endl;
}
int main() {
int t;
init(2e5 + 5);
cin >> t;
while (t--) {
solve();
}
return 0;
}
E.Space Harbour(区间)(Div1-B)
题意:
在一条直线上有 n n n个编号为 1 1 1至 n n n的点。最初有 m m m个港口。第 i i i个海港位于 X i X_i Xi点,其值为 V i V_i Vi。题目保证在 1 1 1点和 n n n点都有港口。将一艘船从当前位置移动到下一个港口的成本是其左侧最近港口的价值与右侧最近港口距离的乘积。具体来说,如果一艘船已经在一个港口,那么将它移动到下一个港口的成本是 0 0 0。
有 q q q个查询,每个查询都是以下 2 2 2种类型中的一种:
- 1 1 1 x x x v v v -在 x x x点添加一个海港,其值为 v v v。保证在添加海港之前, x x x点没有海港。
- 2 2 2 l l l r r r -输出将 l l l至 r r r 点的所有船只移至下一个港口的费用总和。
注意,只需要计算移动船只的费用,而不需要实际移动船只。
分析:
考虑把每两个码头 l , r l,r l,r之间的所有花费看作这个连续段单元 [ l , r ] [l,r] [l,r]的花费,发现对于一对相邻的港口 ( x i , x i + 1 ) (x_i,x_{i+1}) (xi,xi+1), x ∈ ( x i , x i + 1 ) x∈(x_i,x_{i+1}) x∈(xi,xi+1)的花费是 y i ( x i + 1 − x ) y_i(x_{i+1}−x) yi(xi+1−x)。拆开得 y i x i + 1 − y i x y_ix_{i+1}−y_ix yixi+1−yix。
考虑用 s e t set set维护所有港口,这样可以知道一个港口左边和右边的港口的坐标和价值。那么前一项 y i x i + 1 y_ix_{i+1} yixi+1可以线段树区间覆盖,后一项 − y i x −y_ix −yix也可以线段树区间覆盖处理,相当于让一个代表 [ l , r ] [l,r] [l,r]区间的线段树结点的和变成 − y i ( l + r ) ( r − l + 1 ) 2 −y_i\frac{(l+r)(r−l+1)}{2} −yi2(l+r)(r−l+1)。这个标记是可以下传的。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 998244353;
const LL N = 5e5;
const int MAXN = 300005;
LL n, m, q, a[MAXN], b[MAXN];
inline LL calc(LL l, LL r) {
return (l + r) * (r - l + 1) / 2;
}
struct {
LL a[MAXN << 2], tag[MAXN << 2];
inline void pushup(int x) {
a[x] = a[x << 1] + a[x << 1 | 1];
}
inline void init() {
memset(tag, -1, sizeof(tag));
}
inline void pushdown(int x, int l, int r) {
if (tag[x] == -1) {
return;
}
int mid = (l + r) >> 1;
a[x << 1] = tag[x] * (mid - l + 1);
a[x << 1 | 1] = tag[x] * (r - mid);
tag[x << 1] = tag[x << 1 | 1] = tag[x];
tag[x] = -1;
}
void update(int rt, int l, int r, int ql, int qr, LL x) {
if (ql > qr) {
return;
}
if (ql <= l && r <= qr) {
a[rt] = x * (r - l + 1);
tag[rt] = x;
return;
}
pushdown(rt, l, r);
int mid = (l + r) >> 1;
if (ql <= mid) {
update(rt << 1, l, mid, ql, qr, x);
}
if (qr > mid) {
update(rt << 1 | 1, mid + 1, r, ql, qr, x);
}
pushup(rt);
}
LL query(int rt, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) {
return a[rt];
}
pushdown(rt, l, r);
int mid = (l + r) >> 1;
LL res = 0;
if (ql <= mid) {
res += query(rt << 1, l, mid, ql, qr);
}
if (qr > mid) {
res += query(rt << 1 | 1, mid + 1, r, ql, qr);
}
return res;
}
} t1;
struct {
LL a[MAXN << 2], tag[MAXN << 2];
inline void pushup(int x) {
a[x] = a[x << 1] + a[x << 1 | 1];
}
inline void init() {
memset(tag, -1, sizeof(tag));
}
inline void pushdown(int x, int l, int r) {
if (tag[x] == -1) {
return;
}
int mid = (l + r) >> 1;
a[x << 1] = tag[x] * calc(l, mid);
a[x << 1 | 1] = tag[x] * calc(mid + 1, r);
tag[x << 1] = tag[x << 1 | 1] = tag[x];
tag[x] = -1;
}
void update(int rt, int l, int r, int ql, int qr, LL x) {
if (ql > qr) {
return;
}
if (ql <= l && r <= qr) {
a[rt] = x * calc(l, r);
tag[rt] = x;
return;
}
pushdown(rt, l, r);
int mid = (l + r) >> 1;
if (ql <= mid) {
update(rt << 1, l, mid, ql, qr, x);
}
if (qr > mid) {
update(rt << 1 | 1, mid + 1, r, ql, qr, x);
}
pushup(rt);
}
LL query(int rt, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) {
return a[rt];
}
pushdown(rt, l, r);
int mid = (l + r) >> 1;
LL res = 0;
if (ql <= mid) {
res += query(rt << 1, l, mid, ql, qr);
}
if (qr > mid) {
res += query(rt << 1 | 1, mid + 1, r, ql, qr);
}
return res;
}
} t2;
set<pair<LL, LL>> S;
inline void upd(LL x, LL y, LL z) {
t1.update(1, 1, n, x + 1, y - 1, z * y);
t2.update(1, 1, n, x + 1, y - 1, -z);
}
void solve() {
t1.init();
t2.init();
cin >> n >> m >> q;
for (int i = 1; i <= m; ++i) {
cin >> a[i];
}
for (int i = 1; i <= m; ++i) {
cin >> b[i];
S.emplace(a[i], b[i]);
}
for (auto it = S.begin(); next(it) != S.end(); ++it) {
auto jt = next(it);
upd(it->first, jt->first, it->second);
}
while (q--) {
LL op, x, y;
cin >> op >> x >> y;
if (op == 1) {
S.emplace(x, y);
t1.update(1, 1, n, x, x, 0);
t2.update(1, 1, n, x, x, 0);
auto it = S.find(make_pair(x, y));
auto p = prev(it), q = next(it);
upd(p->first, it->first, p->second);
upd(it->first, q->first, it->second);
} else {
cout << t1.query(1, 1, n, x, y) + t2.query(1, 1, n, x, y) << endl;
}
}
}
int main() {
ios::sync_with_stdio(false);
solve();
return 0;
}
F.Fractal Origami(数学)(Div1-C)
题意:
有一张边长等于 1 1 1个单位的正方形纸。在一次操作中,你将正方形的每个角折叠到纸的中心,从而形成边长等于 1 2 \dfrac{1}{\sqrt{2}} 21个单位的另一个正方形。将这个正方形作为一个新的正方形,再次进行操作,一共重复 N N N次。
完成这一系列运算后,打开纸张,看到上面有一些折痕线。每条折痕线都有两种类型:峰或谷。峰是指纸张向外折叠,谷是指纸张向内折叠。
计算纸张上所有山形折痕线的长度总和,将其称为 M M M。同样,计算山谷折痕线的长度,并将其称为 V V V。找出 M V \dfrac{M}{V} VM的值。
可以证明这个值可以用
A
+
B
2
A+B\sqrt{2}
A+B2的形式表示,其中
A
A
A和
B
B
B是有理数。让
B
B
B表示为不可约分数
p
q
\dfrac{p}{q}
qp,输出
p
×
i
n
v
(
q
)
p\times inv(q)
p×inv(q)对
999999893
999999893
999999893取模的结果。
其中
i
n
v
(
q
)
inv(q)
inv(q)是
q
q
q的模数转换
分析:
手动折叠,发现 n = 3 n=3 n=3时, M = 2 + 2 2 , V = 2 + 4 2 M=2+2\sqrt2,V=2+4\sqrt2 M=2+22,V=2+42。于是猜测,第二次折叠开始,每次产生的山谷和山峰的长度相等。
考虑从第二次折叠开始,设当前纸的层数为 k k k(事实上若当前是第 i i i次折叠, k = 2 i − 1 k=2^{i−1} k=2i−1)。则奇数层的纸展开后是山谷,偶数层的纸展开后是山峰。所以 V = M + 2 2 V=M+2\sqrt2 V=M+22恒成立。
这意味着我们只用计算 n n n次折叠后的总折痕长度 V + M V+M V+M,就能算出 M M M和 V V V的值。考虑每次折叠,纸的每一层的折痕长度为上一次折叠时 × 1 2 \times \frac{1}{\sqrt2} ×21,但是纸的层数为上一次折叠时 × 2 \times2 ×2。所以每次折叠,总折痕长度为上一次的 2 \sqrt2 2倍。于是 M = ∑ i = 0 n − 2 2 2 i M=\sum\limits_{i=0}^{n-2}2\sqrt{2}^i M=i=0∑n−222i, V = 2 2 + ∑ i = 0 n − 2 2 2 i V=2\sqrt2+\sum\limits_{i=0}^{n-2}2\sqrt{2}^i V=22+i=0∑n−222i。套用等比求和公式 s = a ( 1 − q n ) 1 − q s=\frac{a(1-q^n)}{1-q} s=1−qa(1−qn)得出答案。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 999999893;
inline LL qpow(LL b, LL p) {
LL res = 1;
while (p) {
if (p & 1) {
res = res * b % mod;
}
b = b * b % mod;
p >>= 1;
}
return res;
}
LL n;
struct Node {
LL a, b;
Node(LL x = 0, LL y = 0) : a(x), b(y) {}
};
inline Node operator+(const Node &a, const Node &b) {
return Node((a.a + b.a) % mod, (a.b + b.b) % mod);
}
inline Node operator-(const Node &a, const Node &b) {
return Node((a.a - b.a + mod) % mod, (a.b - b.b + mod) % mod);
}
inline Node operator*(const Node &a, const Node &b) {
return Node((a.a * b.a + a.b * b.b * 2) % mod, (a.a * b.b + a.b * b.a) % mod);
}
inline Node operator/(const Node &x, const Node &y) {
LL a = x.a, b = x.b, c = y.a, d = y.b;
return Node((a * c - b * d * 2 % mod + mod) % mod * qpow((c * c - d * d * 2 % mod + mod) % mod, mod - 2) % mod,
(b * c - a * d % mod + mod) % mod * qpow((c * c - d * d * 2 % mod + mod) % mod, mod - 2) % mod);
}
inline Node qpow(Node b, LL p) {
Node res(1, 0);
while (p) {
if (p & 1) {
res = res * b;
}
b = b * b;
p >>= 1;
}
return res;
}
void solve() {
cin >> n;
Node a = Node(1, 0) - qpow(Node(0, 1), n - 1), b = Node(1, 0) - Node(0, 1);
Node x = a * Node(2, 0) / b;
Node y = x + Node(0, 2);
Node res = x / y;
cout << res.b << endl;
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
学习交流
以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。