Codeforces Round #598 (Div. 3)

A - Payment Without Change

题意:有a个n元硬币和b个1元硬币,求是否能准确表示出S。

题解:1元硬币全部减去,有个范围,其中是否包含n的倍数。这样太复杂了。还是全部尽可能用n支付,然后剩下的用1补齐。

void test_case() {
    int a, b, n, S;
    scanf("%d%d%d%d", &a, &b, &n, &S);
    S -= min(a, S / n) * n;
    if(S <= b)
        puts("YES");
    else
        puts("NO");
}

B - Minimize the Permutation

题意:给一个排列,每一对格子至多交换一次,求字典序最小的序列。

题解:每次把最小的尽可能往前冒泡。

int a[105];
int pos[105];
bool vis[105];

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        pos[a[i]] = i;
        vis[i] = 0;
    }
    int cur = 1;
    do {
        for(int i = pos[cur]; i >= 2; --i) {
            if(a[i] < a[i - 1] && vis[i] == 0) {
                swap(a[i], a[i - 1]);
                vis[i] = 1;
            } else {
                break;
            }
        }
        ++cur;
    } while(cur <= n);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", a[i], " \n"[i == n]);
}

C - Platforms Jumping

题意:有m块木板,给出它们的长度,越过长n的河,每次可以移动[1,d]格。不能改变木板的相对顺序,可以移动木板,而木板也不能交叉。

题解:贪心,但是不是从正向贪,因为正向可能会使得木板交叉。先把木板堆在右边,然后需要的时候让他平移到刚刚好能接住的位置,这样保证木板不会交叉的同时自己能跳最远。当预定的跳的地点出现木板时,剩下的就是一串长的木板了。

int c[1005];
int ans[1005], beg[1005];

void test_case() {
    int n, m, d;
    scanf("%d%d%d", &n, &m, &d);
    for(int i = 1; i <= m; ++i)
        scanf("%d", &c[i]);

    for(int i = m, p = n; i >= 1; --i) {
        for(int j = p, k = 1; k <= c[i]; --j, ++k) {
            ans[j] = i;
            beg[i] = j;
        }
        p -= c[i];
    }

    int curn = 0, curm = 0;
    while(1) {
        /*for(int i = 1; i <= n; ++i)
            printf("%d%c", ans[i], " \n"[i == n]);*/
        if(curn + d > n || ans[curn + d]) {
            puts("YES");
            for(int i = 1; i <= n; ++i)
                printf("%d%c", ans[i], " \n"[i == n]);
            return;
        } else {
            ++curm;
            if(curm > m) {
                puts("NO");
                return;
            }
            for(int i = beg[curm], k = 1; k <= c[curm]; ++i, ++k)
                ans[i] = 0;
            for(int i = curn + d, k = 1; k <= c[curm]; ++i, ++k) {
                ans[i] = curm;
                curn = i;
            }
        }
    }
}

D - Binary String Minimizing

题意:给一个01串,使用不超过k次临位交换使得这个串最小。

题解:明显是冒泡排序,感觉是优先把最前面的0冒到最头头。那么记录每个0的位置和头的位置就可以了。

char s[1000005];
int pos[1000005], ptop;

void test_case() {
    int n;
    ll k;
    scanf("%d%lld%s", &n, &k, s + 1);
    ptop = 0;
    for(int i = 1; i <= n; ++i) {
        if(s[i] == '0')
            pos[++ptop] = i;
    }
    if(ptop == n) {
        puts(s + 1);
        return;
    }
    int first1 = 1, curp = 1;
    while(s[first1] == '0')
        ++first1, ++curp;
    while(k && curp <= ptop) {
        if(k >= pos[curp] - first1) {
            swap(s[pos[curp]], s[first1]);
            k -= pos[curp] - first1;
        } else {
            swap(s[pos[curp]], s[pos[curp] - k]);
            k = 0;
        }
        ++curp;
        ++first1;
    }
    puts(s + 1);
}

E - Yet Another Division Into Teams

题意:给n个人,分成若干组,每组至少3个人,每组的极差就是该组的价值,求最小的总价值。

题解:假如丢开数据大小来看,这个随便n^2的dp。

dp[i]=min(dp[j]+a[i]-a[j+1]), for each j<=i-3

移项,看起来就是:

dp[i]=min(dp[j]-a[j+1])+a[i], for each j<=i-3

区间RMQ,写个线段树。

还得复原方案,得记录分割点是从哪里来的,真是麻烦。

struct SegmentTree {
#define ls (o<<1)
#define rs (o<<1|1)
    static const int MAXN = 200000;
    //static const int INF = 0x3f3f3f3f;
    int mi[(MAXN << 2) + 5];
    int minid[(MAXN << 2) + 5];

    void PushUp(int o) {
        if(mi[ls] <= mi[rs]) {
            mi[o] = mi[ls];
            minid[o] = minid[ls];
        } else {
            mi[o] = mi[rs];
            minid[o] = minid[rs];
        }
    }

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

    void Update(int o, int l, int r, int ql, int qr, int v) {
        if(ql <= l && r <= qr) {
            mi[o] = v;
        } else {
            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);
        }
    }

    pii QueryMin(int o, int l, int r, int ql, int qr) {
        if(ql > qr)
            return {INF, -1};
        if(ql <= l && r <= qr) {
            return {mi[o], minid[o]};
        } else {
            int m = l + r >> 1;
            pii res = {INF, -1};
            if(ql <= m) {
                pii tmp = QueryMin(ls, l, m, ql, qr);
                if(tmp.first < res.first)
                    res = tmp;
            }
            if(qr >= m + 1) {
                pii tmp = QueryMin(rs, m + 1, r, ql, qr);
                if(tmp.first < res.first)
                    res = tmp;
            }
            return res;
        }
    }
#undef ls
#undef rs
} st;

int n;
pii a[200005];
int dp[200005];
int dp2[200005];

void test_case() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i].first);
        a[i].second = i;
    }
    sort(a + 1, a + 1 + n);
    st.Build(1, 1, n);
    memset(dp, INF, sizeof(dp[0]) * (n + 1));
    dp[0] = 0;
    dp[3] = a[3].first - a[1].first;
    dp2[3] = 1;
    st.Update(1, 1, n, 3, 3, dp[3] - a[4].first);
    for(int i = 4; i <= n; ++i) {
        pii tmp = st.QueryMin(1, 1, n, 3, i - 3);
        dp[i] = tmp.first + a[i].first;
        dp2[i] = tmp.second + 1;
        if(dp[i - 1] - a[i - 1].first + a[i].first < dp[i]) {
            dp[i] = dp[i - 1] - a[i - 1].first + a[i].first;
            dp2[i] = dp2[i - 1];
        }
        st.Update(1, 1, n, i, i, dp[i] - a[i + 1].first);
    }
    printf("%d ", dp[n]);
    int cur = n, pre = dp2[n], cnt = 1;
    while(cur >= 1) {
        if(cur < pre) {
            ++cnt;
            pre = dp2[cur];
        }
        a[cur].first = cnt;
        --cur;
    }
    printf("%d\n", cnt);
    for(int i = 1; i <= n; ++i)
        swap(a[i].first, a[i].second);
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", a[i].second, " \n"[i == n]);
}

再想想这个每次问的区间左端点都固定,改成map不就行了?

map<int, int> st;

int n;
pii a[200005];
int dp[200005];
int dp2[200005];

queue<pii>q;

void test_case() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i].first);
        a[i].second = i;
    }
    sort(a + 1, a + 1 + n);
    memset(dp, INF, sizeof(dp[0]) * (n + 1));
    dp[0] = 0;
    dp[3] = a[3].first - a[1].first;
    dp2[3] = 1;
    q.push({dp[3] - a[4].first, 3});
    for(int i = 4; i <= n; ++i) {
        pii tmp = {INF, -1};
        if(st.size())
            tmp = *st.begin();
        dp[i] = tmp.first + a[i].first;
        dp2[i] = tmp.second + 1;
        if(dp[i - 1] - a[i - 1].first + a[i].first < dp[i]) {
            dp[i] = dp[i - 1] - a[i - 1].first + a[i].first;
            dp2[i] = dp2[i - 1];
        }
        q.push({dp[i] - a[i + 1].first, i});
        if(q.size() >= 3) {
            st[q.front().first] = q.front().second;
            q.pop();
        }
    }
    printf("%d ", dp[n]);
    int cur = n, pre = dp2[n], cnt = 1;
    while(cur >= 1) {
        if(cur < pre) {
            ++cnt;
            pre = dp2[cur];
        }
        a[cur].first = cnt;
        --cur;
    }
    printf("%d\n", cnt);
    for(int i = 1; i <= n; ++i)
        swap(a[i].first, a[i].second);
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", a[i].second, " \n"[i == n]);
}

其实都只是取最小,不是单调栈就可以?

int n;
pii a[200005];
int dp[200005];
int dp2[200005];

stack<pii>st;
queue<pii>q;

void test_case() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i].first);
        a[i].second = i;
    }
    sort(a + 1, a + 1 + n);
    memset(dp, INF, sizeof(dp[0]) * (n + 1));
    dp[0] = 0;
    dp[3] = a[3].first - a[1].first;
    dp2[3] = 1;
    q.push({dp[3] - a[4].first, 3});
    for(int i = 4; i <= n; ++i) {
        pii tmp = {INF, -1};
        if(st.size())
            tmp = st.top();
        dp[i] = tmp.first + a[i].first;
        dp2[i] = tmp.second + 1;
        if(dp[i - 1] - a[i - 1].first + a[i].first < dp[i]) {
            dp[i] = dp[i - 1] - a[i - 1].first + a[i].first;
            dp2[i] = dp2[i - 1];
        }
        q.push({dp[i] - a[i + 1].first, i});
        if(q.size() >= 3) {
            while(st.size() && st.top().first >= q.front().first)
                st.pop();
            st.push(q.front());
            q.pop();
        }
    }
    printf("%d ", dp[n]);
    int cur = n, pre = dp2[n], cnt = 1;
    while(cur >= 1) {
        if(cur < pre) {
            ++cnt;
            pre = dp2[cur];
        }
        a[cur].first = cnt;
        --cur;
    }
    printf("%d\n", cnt);
    for(int i = 1; i <= n; ++i)
        swap(a[i].first, a[i].second);
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", a[i].second, " \n"[i == n]);
}

不对,取前缀最小连单调栈都不需要。

int n;
pii a[200005];
int dp[200005];
int dp2[200005];

pii st = {INF, INF};
queue<pii>q;

void test_case() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i].first);
        a[i].second = i;
    }
    sort(a + 1, a + 1 + n);
    memset(dp, INF, sizeof(dp[0]) * (n + 1));
    dp[0] = 0;
    dp[3] = a[3].first - a[1].first;
    dp2[3] = 1;
    q.push({dp[3] - a[4].first, 3});
    for(int i = 4; i <= n; ++i) {
        dp[i] = st.first + a[i].first;
        dp2[i] = st.second + 1;
        if(dp[i - 1] - a[i - 1].first + a[i].first < dp[i]) {
            dp[i] = dp[i - 1] - a[i - 1].first + a[i].first;
            dp2[i] = dp2[i - 1];
        }
        q.push({dp[i] - a[i + 1].first, i});
        if(q.size() >= 3) {
            if(q.front().first < st.first)
                st = q.front();
            q.pop();
        }
    }
    printf("%d ", dp[n]);
    int cur = n, pre = dp2[n], cnt = 1;
    while(cur >= 1) {
        if(cur < pre) {
            ++cnt;
            pre = dp2[cur];
        }
        a[cur].first = cnt;
        --cur;
    }
    printf("%d\n", cnt);
    for(int i = 1; i <= n; ++i)
        swap(a[i].first, a[i].second);
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", a[i].second, " \n"[i == n]);
}

总结:要先观察DP的式子,然后考虑数据结构的特点。取前缀的可以直接用一个值来维护,转移区间不断右移的可以用单调队列维护,只有当转移区间乱动的时候才需要线段树。在选择线段树的场合,注意维护最小值的坐标的求法。

F - Equalizing Two Strings

题意:有两个等长字符串s,t,每次选择一个长度,把s的这个长度的某区间和t的这个长度的某区间同时翻转,问能否使得s与t相等。

题解:首先排序之后相同才有可能有解,那除此之外什么时候无解呢?设想每次翻转的区间长度都是2,变成一个临位交换,那么一次交换会使得逆序数变化1(冒泡排序),直觉感觉是长的翻转都是分解成这种临位交换的,那么s和t的逆序数会同时变化,所以只需要逆序数的差为偶数,那么可以让逆序数小的那个原地反复交换浪费掉差值。需要注意的是假如有某个字符出现了两次,那么这两个字符在排序之后就可以原地交换浪费掉差值。想不到1A了,说不定这些直觉和乱搞能力已经达到2200了,只是
Graphsforces 要注意学图的知识。

注意归并排序的时候,逆序数应该是longlong,而且只有在弹出j才增加逆序数为剩余的i的数量。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

char s[200005], t[200005], tmp[200005];

ll mergesort(char *s, int l, int r) {
    if(l == r)
        return 0;
    int m = l + r >> 1;
    ll res = 0;
    res += mergesort(s, l, m);
    res += mergesort(s, m + 1, r);
    int i = l, j = m + 1, k = 0;
    while(i <= m || j <= r) {
        if(i > m)
            tmp[++k] = s[j++];
        else if(j > r)
            tmp[++k] = s[i++];
        else if(s[i] < s[j])
            tmp[++k] = s[i++];
        else {
            res += m - i + 1;
            tmp[++k] = s[j++];
        }
    }
    for(int i = 1; i <= k; ++i)
        s[l + i - 1] = tmp[i];
    return res;
}

void test_case() {
    int n;
    scanf("%d%s%s", &n, s + 1, t + 1);
    ll rs = mergesort(s, 1, n);
    ll rt = mergesort(t, 1, n);
    bool suc = 0;
    if(strcmp(s + 1, t + 1) == 0) {
        int tmp = unique(s + 1, s + 1 + n) - (s + 1);
        if(tmp != n || (rs - rt) % 2 == 0)
            suc = 1;
    }
    if(suc)
        puts("YES");
    else
        puts("NO");
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t = 1;
    scanf("%d", &t);
    for(int ti = 1; ti <= t; ++ti) {
        //printf("Case #%d: ", ti);
        test_case();
    }
}
posted @ 2019-11-19 19:58  KisekiPurin2019  阅读(117)  评论(0编辑  收藏  举报