Educational Codeforces Round 6

题目链接:https://codeforces.com/contest/616

A - Professor GukiZ's Robot

送分题。

*B - Grandfather Dovlet’s calculator

题意:求L,R范围内的[0,9]的数字的出现次数。

这题好像可以数位dp,把10种数字分开统计。虽然这个数据范围没必要,但是想写一下这个解法。

数位dp解法:需要注意,当lead标记存在时,统一不统计0的出现次数(即使这个数就是0,也不计算,为了简化程序),然后就是巨复杂的limit标记。当limit标记存在且i==d[pos],则有后面的数位仍然受限,例如:123456,确定了123???,则???的范围是[000,456],要找十进制下低位余数+1。否则,例如123456,确定了122???,则???的范围是[000,999]。

int k;
ll p10[17];

int d[17];
ll r[17];
ll dp[17];

ll dfs(int pos, bool lead, bool limit) {
    if(pos == 0)
        return 0;
    if(!lead && !limit && dp[pos] != -1)
        return dp[pos];
    int up = limit ? d[pos] : 9;
    ll ans = 0;
    for(int i = 0; i <= up; i++) {
        ll D = dfs(pos - 1, lead && i == 0, limit && i == d[pos]);
        ans += D;
        if(i == k) {
            if(lead && i == 0)
                continue;
            if(limit && i == d[pos])
                ans += r[pos - 1] + 1;
            else
                ans += p10[pos - 1];
        }
    }
    if(!lead && !limit)
        dp[pos] = ans;
    return ans;
}

ll solve(ll x) {
    if(x < 0)
        return 0;
    int pos = 0;
    ll cx = x;
    while(cx) {
        d[++pos] = cx % 10;
        cx /= 10;
    }
    for(int i = 1; i <= pos; ++i)
        r[i] = x % p10[i];
    return dfs(pos, true, true);
}

int cost[10] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};

void test_case() {
    p10[0] = 1;
    for(int i = 1; i < 17; ++i)
        p10[i] = p10[i - 1] * 10;
    ll L, R;
    scanf("%lld%lld", &L, &R);
    ll sum = 0;
    for(k = 0; k <= 9; ++k) {
        memset(dp, -1, sizeof(dp));
        ll cnt = solve(R) - solve(L - 1);
        sum += cnt * cost[k];
    }
    printf("%lld
", sum);
}

暴力写法:

int cnt[10];
int cost[10] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};

void test_case() {
    int L, R;
    scanf("%d%d", &L, &R);
    for(int i = L; i <= R; ++i) {
        int x = i;
        while(x) {
            ++cnt[x % 10];
            x /= 10;
        }
    }
    ll sum = 0;
    for(int k = 0; k <= 9; ++k)
        sum += cnt[k] * cost[k];
    printf("%lld
", sum);
}

*C - Pearls in a Row

题意:给一个数组,切分成尽可能多的段,使得每一段都至少有两个相同的元素。

是个简单的贪心尺取,每次有两个相同的就切一段。注意最后一段没有被成功切下来的,不能独立成段,而是合并到最后一段成功切下来的段中。因为题目要求每一段都要是成功切下来的段。

离散化又搞错xtop和n。

int a[300005];
int x[300005], xtop;
int cnt[300005];

int L[300005];
int R[300005];
int top;

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        x[i] = a[i];
    }
    sort(x + 1, x + 1 + n);
    xtop = unique(x + 1, x + 1 + n) - (x + 1);
    for(int i = 1; i <= n; ++i)
        a[i] = lower_bound(x + 1, x + 1 + xtop, a[i]) - x;
    top = 0;
    int l = 1;
    for(int i = 1; i <= n; ++i) {
        ++cnt[a[i]];
        if(cnt[a[i]] == 2) {
            ++top;
            L[top] = l;
            R[top] = i;
            for(int j = l; j <= i; ++j)
                cnt[a[j]] = 0;
            l = i + 1;
        }
    }
    if(top == 0) {
        puts("-1");
        return;
    }
    if(l <= n)
        R[top] = n;
    printf("%d
", top);
    for(int i = 1; i <= top; ++i)
        printf("%d %d
", L[i], R[i]);
}

好像set没有多慢,才比上面的慢50%。

int a[300005];
set<int> s;

int L[300005];
int R[300005];
int top;

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    top = 0;
    int l = 1;
    for(int i = 1; i <= n; ++i) {
        if(s.count(a[i])) {
            ++top;
            L[top] = l;
            R[top] = i;
            s.clear();
            l = i + 1;
        } else
            s.insert(a[i]);
    }
    if(top == 0) {
        puts("-1");
        return;
    }
    if(l <= n)
        R[top] = n;
    printf("%d
", top);
    for(int i = 1; i <= top; ++i)
        printf("%d %d
", L[i], R[i]);
}

所以还是直接用set算了。

*D - Professor GukiZ and Two Arrays

题意:给n(<=2e3)个数字的数组a,和m(<=2e3)个数字的数组b。交换不超过2次,最小化数组a和数组b的和的差的绝对值。每次交换可以把一个数组a的元素和一个数组b的元素交换。

题解:交换0次的,可以直接一边扫出来,交换1次的,可以O(nm)枚举是哪一对发生了交换,立刻扫出来。交换2次的,易知选取的是两对不同的元素。看一下数据范围,考虑使用数据结构。枚举a中的所有的pair,把他们的和的两倍2(ai+aj)作为第一关键字插进数据结构Map中。然后再枚举b中所有的pair。假如一开始的和是sa和sb,那么bi+bj换出之后,要换入哪个ai+aj才是差的绝对值最小呢?易知新的b的和为sb'=sb-(bi+bj)+(ai+aj),新的a的和为sa'=sa-(ai+aj)+(bi+bj),差值d=|sa-sb-2(ai+aj)+2(bi+bj)|,要最小化这个,就应该使得sa-sb+2(bi+bj)与2(ai+aj)尽可能接近。换言之应该在Map中lower_bound找到第一个大于等于的。若等于则直接构造出差值为0的解,否则一定是大于,就把得到的迭代器和前一个迭代器都取出来看看。

Map的写法:

int n, m;
int a[2005], b[2005];
map<ll, pair<int, int> >Map;
 
void test_case() {
    scanf("%d", &n);
    ll sa = 0, sb = 0;
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        sa += a[i];
    }
    scanf("%d",&m);
    for(int j = 1; j <= m; ++j) {
        scanf("%d", &b[j]);
        sb += b[j];
    }
    ll dis = abs(sa - sb);
    if(dis == 0) {
        puts("0");
        puts("0");
        return;
    }
    int k = 0, k1 = -1, k2 = -1, k3 = -1, k4 = -1;
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= m; ++j) {
            ll tmpdis = abs(sa - sb - 2ll * a[i] + 2ll * b[j]);
            if(tmpdis == 0) {
                puts("0");
                puts("1");
                printf("%d %d
", i, j);
                return;
            }
            if(tmpdis < dis) {
                dis = tmpdis;
                k = 1;
                k1 = i;
                k2 = j;
            }
        }
    }
    for(int i = 1; i <= n; ++i) {
        for(int j = i + 1; j <= n; ++j)
            Map[2ll * (a[i] + a[j])] = {i, j};
    }
    for(int i = 1; i <= m; ++i) {
        for(int j = i + 1; j <= m; ++j) {
            ll tmp = sa - sb + 2ll * (b[i] + b[j]);
            auto it = Map.lower_bound(tmp);
            if(it != Map.end()) {
                ll tmpdis = abs(it->first - tmp);
                if(tmpdis < dis) {
                    dis = tmpdis;
                    k = 2;
                    k1 = it->second.first;
                    k2 = i;
                    k3 = it->second.second;
                    k4 = j;
                }
            }
            if(it != Map.begin()) {
                --it;
                ll tmpdis = abs(it->first - tmp);
                if(tmpdis < dis) {
                    dis = tmpdis;
                    k = 2;
                    k1 = it->second.first;
                    k2 = i;
                    k3 = it->second.second;
                    k4 = j;
                }
            }
        }
    }
    printf("%lld
", dis);
    printf("%d
", k);
    if(k == 0)
        return;
    if(k == 1) {
        printf("%d %d
", k1, k2);
        return;
    }
    if(k == 2) {
        printf("%d %d
", k1, k2);
        printf("%d %d
", k3, k4);
        return;
    }
}

可能常数会好一点(3.5倍速)的写法:

int n, m;
int a[2005], b[2005];
pair<ll, pii> Map[4000005];
ll x[4000005];
 
void test_case() {
    scanf("%d", &n);
    ll sa = 0, sb = 0;
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        sa += a[i];
    }
    scanf("%d", &m);
    for(int j = 1; j <= m; ++j) {
        scanf("%d", &b[j]);
        sb += b[j];
    }
    ll dis = abs(sa - sb);
    if(dis == 0) {
        puts("0");
        puts("0");
        return;
    }
    int k = 0, k1 = -1, k2 = -1, k3 = -1, k4 = -1;
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= m; ++j) {
            ll tmpdis = abs(sa - sb - 2ll * a[i] + 2ll * b[j]);
            if(tmpdis == 0) {
                puts("0");
                puts("1");
                printf("%d %d
", i, j);
                return;
            }
            if(tmpdis < dis) {
                dis = tmpdis;
                k = 1;
                k1 = i;
                k2 = j;
            }
        }
    }
    int Mtop = 0;
    for(int i = 1; i <= n; ++i) {
        for(int j = i + 1; j <= n; ++j)
            Map[++Mtop] = make_pair(2ll * (a[i] + a[j]), make_pair(i, j));
    }
    sort(Map + 1, Map + 1 + Mtop);
    for(int i = 1; i <= Mtop; ++i)
        x[i] = Map[i].first;
    for(int i = 1; i <= m; ++i) {
        for(int j = i + 1; j <= m; ++j) {
            ll tmp = sa - sb + 2ll * (b[i] + b[j]);
            int pos = lower_bound(x + 1, x + 1 + Mtop, tmp) - x;
            if(pos <= Mtop) {
                ll tmpdis = abs(Map[pos].first - tmp);
                if(tmpdis < dis) {
                    dis = tmpdis;
                    k = 2;
                    k1 = Map[pos].second.first;
                    k2 = i;
                    k3 = Map[pos].second.second;
                    k4 = j;
                }
            }
            if(pos != 1) {
                --pos;
                ll tmpdis = abs(Map[pos].first - tmp);
                if(tmpdis < dis) {
                    dis = tmpdis;
                    k = 2;
                    k1 = Map[pos].second.first;
                    k2 = i;
                    k3 = Map[pos].second.second;
                    k4 = j;
                }
            }
        }
    }
    printf("%lld
", dis);
    printf("%d
", k);
    if(k == 0)
        return;
    if(k == 1) {
        printf("%d %d
", k1, k2);
        return;
    }
    if(k == 2) {
        printf("%d %d
", k1, k2);
        printf("%d %d
", k3, k4);
        return;
    }
}

实验证明这个Map确实是有够慢的,假如不需要修改的话还是排序之后二分最实在。

*E - New Year Tree

看起来很线段树的一道题。

题意:一棵以1为根的数,每个点带有一种颜色。每次操作:操作1:把一棵v节点下属的子树的颜色全部改变成c;或者操作2:询问一棵v节点下属的子树的颜色的种类。注意只有至多60种颜色。

题解:显然可以按树的dfs序建立线段树。每次对v节点改颜色,就找到v节点子树的范围,进行60次区间改值。然后对于v节点查颜色,就进行60次区间查询。但是感觉复杂度很爆炸。原来这题的idea是用64位整数存这个区间的信息。这样就节约了60次重复的操作。

struct SegmentTree {
#define ls (o<<1)
#define rs (o<<1|1)
    static const int MAXN = 400000;
    ll st[(MAXN << 2) + 5];
    ll lazy[(MAXN << 2) + 5];

    void PushUp(int o) {
        st[o] = st[ls] | st[rs];
    }

    void PushDown(int o, int l, int r) {
        if(lazy[o]) {
            lazy[ls] = lazy[o];
            lazy[rs] = lazy[o];
            int m = l + r >> 1;
            st[ls] = lazy[o];
            st[rs] = lazy[o];
            lazy[o] = 0;
        }
    }

    void Build(int o, int l, int r) {
        if(l == r)
            st[o] = 0;
        else {
            int m = l + r >> 1;
            Build(ls, l, m);
            Build(rs, m + 1, r);
            PushUp(o);
        }
        lazy[o] = 0;
    }

    void Update(int o, int l, int r, int ql, int qr, ll v) {
        if(ql <= l && r <= qr) {
            lazy[o] = v;
            st[o] = v;
            return;
        } else {
            PushDown(o, l, r);
            int m = l + r >> 1;
            if(ql <= m)
                Update(ls, l, m, ql, qr, v);
            if(qr >= m + 1)
                Update(rs, m + 1, r, ql, qr, v);
            PushUp(o);
        }
    }

    ll Query(int o, int l, int r, int ql, int qr) {
        if(ql <= l && r <= qr) {
            return st[o];
        } else {
            PushDown(o, l, r);
            int m = l + r >> 1;
            ll res = 0;
            if(ql <= m)
                res = Query(ls, l, m, ql, qr);
            if(qr >= m + 1)
                res = res | Query(rs, m + 1, r, ql, qr);
            return res;
        }
    }
#undef ls
#undef rs
} st;

int n, q;
int v[400005];
vector<int> G[400005];

int L[400005], R[400005], dtop;

void dfs(int u, int p) {
    L[u] = ++dtop;
    for(auto &v : G[u]) {
        if(v == p)
            continue;
        dfs(v, u);
    }
    R[u] = dtop;
}

void test_case() {
    scanf("%d%d", &n, &q);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &v[i]);
        --v[i];
    }
    for(int i = 1; i <= n - 1; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dtop = 0;
    dfs(1, 0);
    st.Build(1, 1, n);
    for(int i = 1; i <= n; ++i)
        st.Update(1, 1, n, L[i], L[i], 1ll << v[i]);
    while(q--) {
        int op, p, c;
        scanf("%d%d", &op, &p);
        if(op == 1) {
            scanf("%d", &c);
            --c;
            st.Update(1, 1, n, L[p], R[p], 1ll << c);
        } else {
            ll res = st.Query(1, 1, n, L[p], R[p]);
            int cnt = 0;
            while(res) {
                cnt += (res & 1);
                res >>= 1;
            }
            printf("%d
", cnt);
        }
    }
}

提示:
1、dfs序,可以在进入u的时候,把区间左端点标记为u的dfn序,然后遍历其子树,当从其子树返回时,当前的dfn序就是右端点。也就是:

int L[400005], R[400005], dtop;

void dfs(int u, int p) {
    L[u] = ++dtop;
    for(auto &v : G[u]) {
        if(v == p)
            continue;
        dfs(v, u);
    }
    R[u] = dtop;
}

2、只需要知道某颜色是否存在,这个用一个bit就可以表示。

*F - Xors on Segments

这个数据量看起来很像莫队算法。

题意:定义一种函数 (f(l,r)=l oplus l+1 oplus ... oplus r) ,这个函数仅当 (l leq r) 时有定义。给一个 (n(nleq 5 cdot 10^4))数组 (a)(a_i leq 10^6) ,询问 (m(leq 5 cdot 10^3)) 次,每次询问一对 (x,y) ,求出 $maxlimits_{i=x,j=x}^{i leq y,j leq y} f(a_i,a_j) (a_i leq a_j) $

原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12486160.html