Educational Codeforces Round 8 部分题解

C. Bear and String Distance

简单题,直接从距离范围最宽广的字母开始选,每次贪心选最大的。

const int MAXN = 1e5 + 10;
 
int n, k;
char ss[MAXN];
 
struct E { char s, t; int id; } es[MAXN];
 
int dist(char x, char y) { return std::abs(x - y); }
 
bool cmp1(E x, E y) { return x.s > y.s; }
bool cmp2(E x, E y) { return x.id < y.id; }
 
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> k;
    cin >> (ss + 1);
    rep (i, 1, n) { es[i].s = ss[i]; es[i].id = i; }
    std::sort(es + 1, es + 1 + n, cmp1);
    int _validator = 0;
    rep (i, 1, n) {
        int d = std::min(k, std::max(dist('a', es[i].s), dist('z', es[i].s)));
        if (es[i].s + d > 'z') es[i].t = es[i].s - d;
        else es[i].t = es[i].s + d;
        k -= d;
        _validator += dist(es[i].s, es[i].t);
    }
    if (k) {
        cout << -1 << endl; return 0;
    }
    std::sort(es + 1, es + 1 + n, cmp2);
    rep (i, 1, n) cout << es[i].t;
    cout << endl;
    return 0;
}

D. Magic Numbers

数位 DP 基础题,状态是考虑从高到第 \(\mathrm{pos}\) 位,该前缀 \(\bmod 13\) 的值,是否触碰上限。

因为保证上下界数字长度相同所以就不需要考虑前导零了,问题简化了很多。

别忘了开 long long。

const int MAXN = 2000 + 10;
const int HA = 1e9 + 7;
 
int m, d;
 
char ss[MAXN];
 
int divnum[MAXN], cnt;
 
lli dp[MAXN][MAXN];
lli dfs(int pos, int rmd, bool limit, int len) {
    if (pos == 0) return rmd == 0;
    if (!limit && dp[pos][rmd] != -1) return dp[pos][rmd];
    lli ans = 0;
    int top = limit ? divnum[pos] : 9;
    for (int i = 0; i <= top; ++i) {
        if (((len - pos + 1) % 2 == 0) ^ (i == d)) continue;
        ans += dfs(pos - 1, (rmd * 10 + i) % m, limit && (i == divnum[pos]), len);
        ans %= HA;
    }
    if (!limit) dp[pos][rmd] = ans;
    return ans;
}
void divide() {
    int len = (int) strlen(ss + 1);
    cnt = 0;
    for (int i = len; i >= 1; --i) divnum[++cnt] = ss[i] - '0';
}
lli Solve() {
    divide();
    memset(dp, -1, sizeof dp);
    return dfs(cnt, 0, true, cnt);
}
 
bool check() {
    int bmod = 0;
    for (int i = 1; i <= cnt; ++i) {
        if ((i % 2 == 0) ^ ((ss[i] - '0') == d)) return false;
        bmod = bmod * 10 + ss[i] - '0';
        bmod %= m;
    }
    return bmod == 0;
}
 
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> m >> d;
    cin >> (ss + 1);
    lli ans1 = Solve() - check();
    (ans1 += HA) %= HA;
    // DEBUG(ans1);
    cin >> (ss + 1);
    lli ans2 = Solve();
    // DEBUG(ans2);
    cout << (ans2 - ans1 + HA) % HA << endl; 
    return 0;
}

E. Zbazi in Zeydabad

挺有意思一题。

定义 z 线:指一条连续的全部由字符 z 组成的线段,可能是斜的也可能是横的。

首先我们可以预处理出所有点往左 tl[i][j]、往右 tr[i][j],往左下 tld[i][j] 的最长横 z 线长度,这对于我们判断 Z 字形有很大的用处。

注意到 Z 字形的结构是正方形,所以确定了一个角和长度就可以确定整个图形的位置;而且对角线角(左下、右上)是和图形的对角线部分直接相关的,所以考虑枚举图形的右上角 \((i, j)\)

可以发现,这个图形可能的最大长度即是往左和往左下 z 线长度的较小值 l = min(tl[i][j], tld[i][j]),于是接下来我们的任务就是判断这条长为 \(l\) 的斜 z 线上有多少满足,经过它的横 z 线向右伸展达到(或超过)这个右上角所在列 \(j\),的点,以这些点作为左下角即可确定一个 Z 字形。直接枚举是 \(O(n^3)\) 级别的,考虑能不能优化。

5bfa7Q.png

注意到以我们枚举的点为右上角的所有 Z 字形有两个性质:

它们下面那一条横 z 线都至少向右伸展达到(或超过)了右上角所在列 \(j\)
它们所有的左下角一定分布在这条长为 \(l\) 的斜 z 线上,也就是在对角线上的一个区间查询。

因此我们可以考虑去寻找所有右端点在 \(j\) 上(或右边)的横 z 线,它们的左端点如果在这条对角线斜 z 线上,就说明这个左端点是满足要求的一个左下角。

这个所有线是 \(O(n^3)\) 级别的,但是发现相同左端点的横 z 线只会产生一个贡献,而且右端点肯定尽量靠右最好,所以我们就只保留每一个点往右伸展最长长度的横 z 线,也就是对于点 \((x, y)\),我们要存的线段是 \((x, y) \rightarrow (x, y + \mathrm{tr}_{x, y} - 1)\)(如果存在的话)。

接下来的任务就是,对于这个右上角 \((i, j)\),枚举所有右端点也在列 \(j\) 上(或超过)的线段,判断它的左端点是不是在这条对角线上,而且属于这个长为 \(l\) 的区间。

类似树状数组求逆序对,这里我们也可以使用类似于边插入边查询的思想,考虑对每一条对角线维护一个树状数组,\(b_{x + y, y}\) 表示当前\(x + y\) 条对角线上是否存在以第 \(y\) 列的点为左端点的线段(0 或 1),通过以合适的顺序枚举线段并修改树状数组,我们可以实现在对角线上的区间查询。

为了满足线段所有右端点都在当前枚举的列右边,我们修改枚举右上角 \((i, j)\) 的顺序,先从右往左枚举列 \(j\),找到所有右端点在列 \(j\) 上的线段,将左端点插入对应对角线的树状数组;再从上到下枚举行 \(i\),确定右上角 \((i, j)\),在对应对角线的树状数组上进行区间查询,累计答案即可。

const int MAXN = 3000 + 10;

int n, m;
char mat[MAXN][MAXN];

int tl[MAXN][MAXN], tr[MAXN][MAXN], tld[MAXN][MAXN];
std::vector<std::pair<int, int> > segs[MAXN]; // segs[r][x] -> LeftNode(i, j)
void pre() {
    rep (i, 1, n) {
        rep (j, 1, m) if (mat[i][j] == 'z') tl[i][j] = tl[i][j - 1] + 1;
        for (int j = m; j >= 1; --j) if (mat[i][j] == 'z') tr[i][j] = tr[i][j + 1] + 1;
    }
    for (int i = n; i >= 1; --i) {
        rep (j, 1, m) if (mat[i][j] == 'z') tld[i][j] = tld[i + 1][j - 1] + 1;
    }
    rep (i, 1, n) {
        rep (j, 1, m) {
            int r = j + tr[i][j] - 1;
            if (j <= r) segs[r].push_back({i, j});
        }
    }
}

struct BIT {
    lli seq[MAXN];
    #define lb(x) ((x) & (-(x)))
    void mo(int x) {
        for (; x <= m; x += lb(x)) ++seq[x];
    }
    lli qr(int x) {
        int r = 0; for (; x; x -= lb(x)) r += seq[x]; return r;
    }
} diagonals[MAXN << 1];

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> m;
    rep (i, 1, n) cin >> (mat[i] + 1);
    pre();
    lli ans = 0;
    for (int j = m; j >= 1; --j) {
        for (auto seg : segs[j]) {
            int x = seg.fi, y = seg.se;
            diagonals[x + y].mo(y);
        }
        for (int i = 1; i <= n; ++i) {
            // (i, j) as the upperright corner
            if (mat[i][j] != 'z') continue;
            int maxlen = std::min(tl[i][j], tld[i][j]);
            int ql = j - maxlen + 1, qr = j;
            ans += diagonals[i + j].qr(qr) - diagonals[i + j].qr(ql - 1);
        }
    }
    printf("%lld\n", ans);
    return 0;
}

F

需要网络流相关知识

posted @ 2021-10-28 07:26  Handwer  阅读(65)  评论(0编辑  收藏  举报