2016-2017 7th BSUIR Open Programming Contest. Final 补题

题目链接

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 2n3 无解;否则,令 a i , j = ( i − 1 ) ∗ n + j a_{i, j} = (i - 1) * n + j ai,j=(i1)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,j1ai1,j2=ai2,j1ai2,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(29n,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 l1,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,x1] 取最大的后缀和, 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+1pai,其中 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=lrai+i=1maxl1aii=lminr},迭代 r r r,用线段树维护该值: r − 1 r-1 r1 r r r 时,对于 ∑ i = l r a i \sum\limits_{i = l}^{r} a_i i=lrai 这一项,只需要对 [ 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 ansi1% 2i,若余数为 2 i − 1 2^{i - 1} 2i1,则加上 1 0 i − 1 10^{i - 1} 10i1 使得余数为 0 0 0;若余数为 0 0 0,可不操作,或加上 2 × 1 0 i − 1 2×10^{i - 1} 2×10i1

参考代码:
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值