题目链接
https://codeforces.com/gym/102133
参考题解
A - Tree Orientation
简要题意:
给定 n n n 个结点的无向树,根为 1 1 1 号点,问有多少种将边定向的方案,使得出度为 0 0 0 的点恰有 m m m 个。
解题思路:
考虑 d p dp dp,每个结点考虑其到父结点的边的定向情况, f p [ u ] [ i ] fp[u][i] fp[u][i] 表示 u u u 子树内, u u u 结点的边指向父结点时,恰有 i i i 个出度为 0 0 0 的点的方案数;同理 f d [ u ] [ i ] [ 0 / 1 ] fd[u][i][0/1] fd[u][i][0/1] 表示将边指向 u u u 结点,这里需要额外加一维表示 u u u 是否出度为 0 0 0。转移时逐子树合并转移,复杂度分析同树上背包。状态转移见代码。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
vector<int> G[maxn];
int siz[maxn];
ll fd[maxn][maxn][2], fp[maxn][maxn];
int n, m;
void dfs(int u, int f){
siz[u] = 1, fd[u][1][1] = fp[u][0] = 1;
for(auto &v : G[u]){
if(v == f) continue;
dfs(v, u);
memset(fd[0], 0, sizeof fd[0]);
memset(fp[0], 0, sizeof fp[0]);
for(int i = siz[u]; i >= 0; --i){
for(int j = siz[v]; j >= 0; --j){
(fd[0][i + j][0] += fd[u][i][0] * (fd[v][j][0] + fd[v][j][1] + fp[v][j])) %= mod;
if(i + j > 0) (fd[0][i + j - 1][0] += fd[u][i][1] * (fd[v][j][0] + fd[v][j][1])) %= mod;
(fd[0][i + j][1] += fd[u][i][1] * fp[v][j]) %= mod;
(fp[0][i + j] += fp[u][i] * (fd[v][j][0] + fd[v][j][1] + fp[v][j])) %= mod;
}
}
memcpy(fd[u], fd[0], sizeof fd[0]);
memcpy(fp[u], fp[0], sizeof fp[0]);
siz[u] += siz[v];
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for(int i = 1; i < n; ++i){
int u, v; cin >> u >> v;
G[u].pb(v), G[v].pb(u);
}
dfs(1, 0);
ll ret = (fd[1][m][0] + fd[1][m][1]) % mod;
cout << ret << endl;
return 0;
}
B - A Masterpiece
简要题意:
给一个数 n n n,求构造 n × n n×n n×n 的矩阵,满足数 1 1 1 到 n 2 n^2 n2 分别出现一次,并且任意相邻的数差值大于 1 1 1,且任意一组 n n n 个行下标不同、且列下标不同的数之和相同。
解题思路:
n = 1 n = 1 n=1 时单走一个 1 1 1; 2 ≤ n ≤ 3 2 \leq n \leq 3 2≤n≤3 无解;否则,令 a i , j = ( i − 1 ) ∗ n + j a_{i, j} = (i - 1) * n + j ai,j=(i−1)∗n+j,则仅不满足行内相邻数差值大于 1 1 1 这个条件。由和值相同的条件得到任意 a i 1 , j 1 − a i 1 , j 2 = a i 2 , j 1 − a i 2 , j 2 a_{i1,j1} - a_{i1, j2} = a_{i2, j1} - a_{i2, j2} ai1,j1−ai1,j2=ai2,j1−ai2,j2,故仅需要对列进行交换调整答案,一个可行方案为先排列偶数列、再排奇数列。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e2 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int n;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
if(n == 1){
cout << "1" << endl;
cout << "1" << endl;
}
else if(n <= 3){
cout << "-1" << endl;
}
else{
cout << n << endl;
for(int i = 1; i <= n; ++i){
int x = (i - 1) * n;
for(int j = 2; j <= n; j += 2) cout << x + j << " ";
for(int j = 1; j <= n; j += 2) cout << x + j << " ";
cout << endl;
}
}
return 0;
}
C - Auction
简要题意:
给定一个数 n n n,现有 x = 1 x = 1 x=1, A B AB AB 两人轮流对 x x x 乘上 2 2 2 到 9 9 9 中的一个数, A A A 先手,当 x > n x > n x>n 时停止游戏,无法操作的一方输。问是否先手必胜。
解题思路:
逆向考虑,当 x > n x \gt n x>n 为必败态, x ∈ ( ⌊ n 9 ⌋ , n ] x \in (\lfloor\frac{n}{9}\rfloor, n] x∈(⌊9n⌋,n] 为必胜态(存在一种 × 9 ×9 ×9 的操作使得对方必败), x ∈ ( ⌊ ⌊ n 9 ⌋ 2 ⌋ , ⌊ n 9 ⌋ ] x \in (\lfloor\cfrac{\lfloor\frac{n}{9}\rfloor}{2}\rfloor, \lfloor\frac{n}{9}\rfloor] x∈(⌊2⌊9n⌋⌋,⌊9n⌋] 为必败态(无论乘上多少对方都落入前面那个必胜态),以此类推。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e2 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T;
while(T--){
ll n; cin >> n;
ll l = n / 9, r = n, d = 9, ret = 1;
while(l >= 1){
d = d == 2 ? 9 : 2;
r = l, l /= d, ret ^= 1;
// cout << l << " " << r << " " << ret << endl;
}
cout << (ret ? "YES" : "NO") << endl;
}
return 0;
}
F - Financial Reports
简要题意:
给一个数组 a a a,求在进行一次交换操作后的最大子段和,并输出交换方案,交换下标须不同、最大子段和区间非空。
解题思路:
交换后最大子段和必然不减,若最大子段和不变,如果区间长为 1 1 1,则任意交换都可以成为答案;若区间长度大于 1 1 1,交换最大子段和内部两个下标。否则,考虑答案有增加的交换操作,考虑枚举交换的 O ( n 2 ) O(n^2) O(n2) 种情况,对答案有增量意味着交换的其中一个下标 x x x 落在某最大子段和 [ l , r ] [l, r] [l,r] 内部、或恰处于边界 l − 1 , r + 1 l-1, r+1 l−1,r+1 处。故可以枚举下标 x x x,讨论另一个交换下标 y y y:若 y > x y \gt x y>x,则 x x x 左侧 [ 1 , x − 1 ] [1, x - 1] [1,x−1] 取最大的后缀和, x x x 右侧 [ x + 1 , n ] [x + 1, n] [x+1,n] 取最大的 a y + ∑ i = x + 1 p a i a_y + \sum\limits_{i=x + 1}^{p} a_i ay+i=x+1∑pai,其中 p < y p \lt y p<y。 x < y x \lt y x<y 的情况同理。用线段树区间合并可以维护。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<ll, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int a[maxn], n;
template<class T>
T max(const T &a, const T &b, const T &c){
return max(a, max(b, c));
}
struct Node{
ll sum, pmx, lmx, rmx, lpmx, prmx;
int p, l, r, lp, rp;
friend Node operator + (const Node &a, const Node &b){
Node c;
c.sum = a.sum + b.sum;
tie(c.pmx, c.p) = max(pii(a.pmx, a.p), pii(b.pmx, b.p));
tie(c.lmx, c.l) = max(pii(a.lmx, a.l), pii(a.sum + b.lmx, b.l));
tie(c.rmx, c.r) = max(pii(b.rmx, b.r), pii(b.sum + a.rmx, a.r));
tie(c.lpmx, c.lp) = max(pii(a.lpmx, a.lp), pii(a.lmx + b.pmx, b.p), pii(a.sum + b.lpmx, b.lp));
tie(c.prmx, c.rp) = max(pii(b.prmx, b.rp), pii(b.rmx + a.pmx, a.p), pii(b.sum + a.prmx, a.rp));
return c;
}
} tr[maxn << 2];
struct Ans{
ll v; int x, y;
bool operator < (const Ans &o) const{
return v < o.v;
}
} ans;
void pushUp(int rt){
tr[rt] = tr[lson] + tr[rson];
}
void build(int l, int r, int rt){
if(l == r){
tr[rt].sum = tr[rt].pmx = tr[rt].lpmx = tr[rt].prmx = a[l];
tie(tr[rt].lmx, tr[rt].l) = max(pii(a[l], l), pii(0, l - 1));
tie(tr[rt].rmx, tr[rt].r) = max(pii(a[r], r), pii(0, r + 1));
tr[rt].p = tr[rt].lp = tr[rt].rp = l;
return;
}
int mid = gmid;
build(l, mid, lson);
build(mid + 1, r, rson);
pushUp(rt);
}
Node query(int l, int r, int rt, int L, int R){
// cout << rt << " " << l << " ?? " << r << " " << tr[rt].prmx << " " << tr[rt].rp << endl;
if(l >= L && r <= R) return tr[rt];
int mid = gmid;
if(L <= mid && R > mid) return query(l, mid, lson, L, R) + query(mid + 1, r, rson, L, R);
else if(L <= mid) return query(l, mid, lson, L, R);
else return query(mid + 1, r, rson, L, R);
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
build(1, n, 1);
Ans ans = Ans{a[1], 1, 1};
for(int i = 1; i <= n; ++i){
Node ln = i > 1 ? query(1, n, 1, 1, i - 1) : Node();
Node rn = i < n ? query(1, n, 1, i + 1, n) : Node();
if(i > 1) ans = max(ans, Ans{ln.rmx + a[i], i, ln.r});
if(i > 1) ans = max(ans, Ans{ln.prmx + rn.lmx, ln.rp, i});
if(i < n) ans = max(ans, Ans{ln.rmx + rn.lpmx, i, rn.lp});
// if(i == 4) cout << ln.prmx << " ?? " << ln.rp << endl;
// cout << i << " " << ans.v << " " << ans.x << " " << ans.y << endl;
}
if(ans.x == ans.y) ans.x = 1, ans.y = n;
cout << ans.v << endl;
cout << ans.x << " " << ans.y << endl;
return 0;
}
我写的另一种比较烦的做法,可以用来练习。枚举
O
(
n
2
)
O(n^2)
O(n2) 个子段和,考虑最大化答案的情况,即内部找一个最小值与外部的最大值交换。考虑与子段内与左边交换的情况,另一种情况将序列反转后同理。枚举
r
r
r,求
max
l
=
1
r
{
∑
i
=
l
r
a
i
+
max
i
=
1
l
−
1
a
i
−
min
i
=
l
r
}
\max\limits_{l = 1}^{r}\{\sum\limits_{i = l}^{r} a_i + \max\limits_{i = 1}^{l - 1} a_i - \min\limits_{i = l}^{r}\}
l=1maxr{i=l∑rai+i=1maxl−1ai−i=lminr},迭代
r
r
r,用线段树维护该值:
r
−
1
r-1
r−1 到
r
r
r 时,对于
∑
i
=
l
r
a
i
\sum\limits_{i = l}^{r} a_i
i=l∑rai 这一项,只需要对
[
1
,
r
]
[1, r]
[1,r] 加上
a
r
a_r
ar;最大值项对应一次单点加;最小值项用单调栈维护,每次修改为区间加法。
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
typedef pair<ll, int> pli;
const ll oo = 1ll << 60;
struct Node{
ll val; int x, y;
bool operator < (const Node &o) const{
return val < o.val;
}
} ans1, ans2;
ll a[maxn], b[maxn], sum[maxn]; pli mx[maxn];
pli stk[maxn]; int top;
int n;
struct SegTree{
pli mx[maxn << 2], mn[maxn << 2]; ll add[maxn << 2];
void pushUp(int rt){
mx[rt] = max(mx[lson], mx[rson]);
mn[rt] = min(mn[lson], mn[rson]);
}
void build(int l, int r, int rt){
add[rt] = 0;
if(l == r){
mx[rt] = mn[rt] = {0, l};
return;
}
int mid = gmid;
build(l, mid, lson);
build(mid + 1, r, rson);
pushUp(rt);
}
void pushDown(int rt){
if(add[rt]){
add[lson] += add[rt], add[rson] += add[rt];
mx[lson].first += add[rt], mx[rson].first += add[rt];
mn[lson].first += add[rt], mn[rson].first += add[rt];
add[rt] = 0;
}
}
void update(int l, int r, int rt, int L, int R, ll val){
if(l >= L && r <= R){
add[rt] += val;
mx[rt].first += val;
mn[rt].first += val;
return;
}
int mid = gmid; pushDown(rt);
if(L <= mid) update(l, mid, lson, L, R, val);
if(R > mid) update(mid + 1, r, rson, L, R, val);
pushUp(rt);
}
pli queryMx(int l, int r, int rt, int L, int R){
if(l >= L && r <= R) return mx[rt];
int mid = gmid; pli ret = {-oo, 0}; pushDown(rt);
if(L <= mid) ret = max(ret, queryMx(l, mid, lson, L, R));
if(R > mid) ret = max(ret, queryMx(mid + 1, r, rson, L, R));
return ret;
}
pli queryMn(int l, int r, int rt, int L, int R){
if(l >= L && r <= R) return mn[rt];
int mid = gmid; pli ret = {oo, 0}; pushDown(rt);
if(L <= mid) ret = min(ret, queryMn(l, mid, lson, L, R));
if(R > mid) ret = min(ret, queryMn(mid + 1, r, rson, L, R));
return ret;
}
} tr_a, tr_sum, tr;
void solve(Node &ans){
sum[0] = 0, mx[0] = {-oo, 0};
for(int i = 1; i <= n; ++i){
sum[i] = sum[i - 1] + a[i];
mx[i] = max(mx[i - 1], pli{a[i], i});
}
stk[top = 0] = {-oo, 0};
tr_a.build(1, n, 1);
tr_sum.build(1, n, 1);
tr.build(1, n, 1);
for(int i = 1; i <= n; ++i){
tr_a.update(1, n, 1, i, i, a[i]);
tr_sum.update(1, n, 1, 1, i, a[i]);
tr.update(1, n, 1, 1, i, a[i]);
tr.update(1, n, 1, i, i, mx[i - 1].first - a[i]);
while(a[i] <= stk[top].first){
pli er = stk[top--], el = stk[top];
tr.update(1, n, 1, el.second + 1, er.second, er.first - a[i]);
}
stk[++top] = {a[i], i};
pli e = tr_sum.queryMx(1, n, 1, 1, i);
ll s = sum[i]; int l = e.second;
Node tmp = Node{s, l, i};
if(ans < tmp) ans = tmp;
if(i == 1) continue;
e = tr.queryMx(1, n, 1, 2, i);
s = e.first, l = e.second;
int pl = mx[l - 1].second, pr = tr_a.queryMn(1, n, 1, l, i).second;
tmp = Node{s, pl, pr};
if(ans < tmp) ans = tmp;
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
ans1.val = ans2.val = -oo;
solve(ans1);
reverse(a + 1, a + 1 + n);
solve(ans2);
ans2.x = n - ans2.x + 1;
ans2.y = n - ans2.y + 1;
if(ans1 < ans2) ans1 = ans2;
if(ans1.x == ans1.y) ans1.x = 1, ans1.y = n;
cout << ans1.val << endl;
cout << ans1.x << " " << ans1.y << endl;
return 0;
}
G - Moore’s Law
简要题意:
给定 n n n,构造一个数 x x x 满足 x x x 十进制表示仅包含 1 1 1 和 2 2 2,且 x x x 是 2 n 2^n 2n 的倍数。
解题思路:
递推构造, a n s 1 = 2 ans_1 = 2 ans1=2,对于 i > 1 i \gt 1 i>1,讨论 a n s i − 1 % 2 i ans_{i - 1} \%~ 2^i ansi−1% 2i,若余数为 2 i − 1 2^{i - 1} 2i−1,则加上 1 0 i − 1 10^{i - 1} 10i−1 使得余数为 0 0 0;若余数为 0 0 0,可不操作,或加上 2 × 1 0 i − 1 2×10^{i - 1} 2×10i−1。
参考代码:
ans = [0 for i in range(43)]
ans[1] = '2'
for i in range(2, 43):
if int(ans[i - 1]) % 2**i == 0:
ans[i] = '2' + ans[i - 1]
else:
ans[i] = '1' + ans[i - 1]
n = int(input())
print(ans[n])
I - Number builder
简要题意:
签到题。
解题思路:
分类讨论。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e2 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int n; cin >> n;
int s = n % 3 == 1 ? 1 : 2;
do{
cout << s;
n -= s, s = 3 - s;
}
while(n > 0);
cout << endl;
return 0;
}