Codeforces Round #592 (Div. 2)

A - Pens and Pencils

题意:有a堂作文课和b次练习课,每1支钢笔可以用c堂作文课,每1支铅笔可以用d堂练习课。求携带笔不超过k支的方案。

题解:签到题,就要大大方方地签。不搞这么多绕来绕去的。

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

void test_case() {
    int a, b, c, d, k;
    scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
    int ac = (a + (c - 1)) / c, ad = (b + (d - 1)) / d;
    if(ac + ad > k)
        puts("-1");
    else
        printf("%d %d\n", ac, ad);
}

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();
    }
}

B - Rooms and Staircases

题意:有两层楼的房间,[1,n]排一列,每次可以左右走,某些位置有楼梯。在不经过一个房间两次的前提下任选起点和路径,求最大经过的房间。

题解:又签到,很明显是从一个尽头走到最远的楼梯上楼再回去,枚举两个尽头。

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

char s[1005];

void test_case() {
    int n;
    scanf("%d%s", &n, s + 1);
    int l = 1, r = n;
    while(l <= n) {
        if(s[l] == '0')
            ++l;
        else
            break;
    }
    while(r >= 1) {
        if(s[r] == '0')
            --r;
        else
            break;
    }
    if(l > r)
        printf("%d\n", n);
    else
        printf("%d\n", 2 * n - 2 * min(n - r, l - 1));
}

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();
    }
}

C - The Football Season

题意:有n场球,一个队得了p分,已知赢一次得w分,平一次得d分,输不得分,且w>d。求一个解,无解输出-1。

题解:不就是求xw+yd=p的解,然后要求x+y<=n吗?套个扩展欧几里得求出最小的非负x,显然最小的非负x不能再减小,而减小y必定使得x增大,由于w>d所以要使得得分保持不变y减少x增量会少一些。这就复杂了,反过来解这个方程,则xd+yw=p。

注:这个题让我发现我模板里解线性同余方程会溢出longlong的bug,在不能使用int128的情况下很麻烦。通过一些分析我修复了这个问题,常数增大了一点,但是不容易溢出。具体详见模板。

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

ll gcd(ll a, ll b) {
    if(b == 0)
        return a;
    while(ll t = a % b)
        a = b, b = t;
    return b;
}

ll ex_gcd(ll a, ll b, ll& x, ll& y) {
    if(b == 0) {
        x = 1, y = 0;
        return a;
    }
    ll d = ex_gcd(b, a % b, x, y), t;
    t = x, x = y, y = t - a / b * y;
    return d;
}

//解线性同余方程 ax + by = c ,无解返回false
bool _LCE(ll a, ll b, ll c, ll &x0, ll &y0) {
    ll x, y, d = ex_gcd(a, b, x, y);
    if(c % d)
        return false;
    ll k = b / gcd(a, b);
    x0 = ((x % k) * (c / d % k) % k + k) % k;
    y0 = (c - a * x0) / b;
    //x0是x的最小非负整数解
    //x=x0+b*t,y=y0-a*t,是方程的所有解,对所有整数t成立
    return true;
}

//解线性同余方程 ax = b mod n ,无解返回false
//和方程 ax + ny = b 等价
bool LCE(ll a, ll b, ll n, ll &x0) {
    ll x, y;
    if(_LCE(a, n, b, x, y)) {
        ll k = n / gcd(a, n);
        x0 = (x % k + k) % k;
        //x0是最小非负整数解
        //x=x0+k*t,是方程的所有解,对所有整数t成立
        return true;
    } else
        return false;
}

void test_case() {
    ll n, p, w, d, x, y, z = -1;
    scanf("%lld%lld%lld%lld", &n, &p, &w, &d);
    if(_LCE(d, w, p, y, x))
        z = n - x - y;
    if(x >= 0 && z >= 0)
        printf("%lld %lld %lld\n", x, y, z);
    else
        puts("-1");
}

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();
    }
}

但是这个太草了,其实只需要枚举一个数,另一个数直接除下去就可以。

void test_case() {
    ll n, p, w, d;
    scanf("%lld%lld%lld%lld", &n, &p, &w, &d);
    for(ll x = 0; x * w <= p; ++x) {
        if((p - x * w) % d)
            continue;
        ll y = (p - x * w) / d;
        ll z = n - x - y;
        if(z >= 0) {
            printf("%lld %lld %lld", x, y, z);
            return;
        }
    }
    puts("-1");
    return;
}

这样会T,都不看范围的?其实是经过严密的观察发现,w个d和d个w是等价的,而把w个d换成d个w可以节省n,多出来的丢给z,所以y只有[0,m-1]这样的取值。还是封装一个带mod的大数比较靠谱。

void test_case() {
    ll n, p, w, d;
    scanf("%lld%lld%lld%lld", &n, &p, &w, &d);
    for(ll y = 0; y < w && y * d <= p; ++y) {
        if((p - y * d) % w)
            continue;
        ll x = (p - y * d) / w;
        ll z = n - x - y;
        if(z >= 0) {
            printf("%lld %lld %lld", x, y, z);
            return;
        }
    }
    puts("-1");
    return;
}

D - Paint the Tree

树形dp?求每个点染成某色的最小代价?但是还和孙子节点有关,想到这里的时候就发现dp要记录孙子的两个颜色,假如孙子有两个颜色那么儿子的颜色就确定了,但是这个点本身就没办法涂了,所以孙子只能有1种颜色。

题意:给一棵树染3种色其中之一,使得任意一条长为2的简单路径上的3个点的颜色都不同。这样就要求树没有分叉,否则有一个点度为3,那么不可能涂色完成。那么就是给一条链涂色,随便dp一下。

注意一定要分清楚换名之后的id和原id,以及现在在操作哪个id。

int a[100005][3];
int deg[100005];
int id[100005];
int aid[100005];
vector<int> G[100005];
ll dp[100005][3][3];

int cnt = 0;
void dfs(int u, int p) {
    id[u] = ++cnt;
    aid[cnt] = u;
    for(auto &v : G[u]) {
        if(v == p)
            continue;
        dfs(v, u);
    }
}

int op(int i, int j) {
    if(i == 0)
        return j == 1 ? 2 : 1;
    if(i == 1)
        return j == 0 ? 2 : 0;
    return j == 0 ? 1 : 0;
}

int ans[100005];

void test_case() {
    int n;
    scanf("%d", &n);
    rep(i, 1, n) scanf("%d", &a[i][0]);
    rep(i, 1, n) scanf("%d", &a[i][1]);
    rep(i, 1, n) scanf("%d", &a[i][2]);
    rep(i, 1, n - 1) {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
        ++deg[u], ++deg[v];
        if(deg[u] >= 3 || deg[v] >= 3) {
            puts("-1");
            return;
        }
    }

    int root = -1;
    rep(i, 1, n) {
        if(deg[i] == 1) {
            root = i;
            break;
        }
    }
    dfs(root, -1);
    memset(dp, INF, sizeof(dp));
    for(int i = 0; i <= 2; ++i) {
        for(int j = 0; j <= 2; ++j) {
            if(i == j)
                continue;
            dp[2][i][j] = a[aid[1]][i] + a[aid[2]][j];
        }
    }
    rep(k, 3, n) {
        for(int i = 0; i <= 2; ++i) {
            for(int j = 0; j <= 2; ++j) {
                if(i == j)
                    continue;
                dp[k][i][j] = dp[k - 1][op(i, j)][i] + a[aid[k]][j];
            }
        }
    }
    ll ANS = 1ll * INF * INF;
    int c1, c2;
    for(int i = 0; i <= 2; ++i) {
        for(int j = 0; j <= 2; ++j) {
            if(i == j)
                continue;
            if(dp[n][i][j] < ANS) {
                ANS = dp[n][i][j];
                c1 = j;
                c2 = i;
            }
        }
    }
    printf("%lld\n", ANS);
    ans[n] = c1;
    ans[n - 1] = c2;
    per(i, n - 2, 1) ans[i] = op(ans[i + 1], ans[i + 2]);
    rep(i, 1, n) printf("%d%c", ans[id[i]] + 1, " \n"[i == n]);
    return;
}

E - Minimizing Difference

题意:给一个序列,每次操作可以选一个元素+1或者选一个元素-1,求不超过k次操作后生成的最小差。

题解:数据小的话可以贪心,每次选最大和最小里面出现次数较少的那个集体变化。这样每次操作会合并至少一个元素或者把k消耗到几乎殆尽。假如我二分一个极差k,然后尺取移动这个极差就可以转移出需要的值,所以二分+尺取是对的,先写这个解。

朴素的二分+尺取没有办法解决最后的边界不出现在ai中的情形。

作出一个假设,最优解的其中一种构造必定以ai为其中之一边界。这个结论看起来就很显然!得到这个结论之后需要进行正反两次check。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
#define rep(i,p1,p2) for(int i=(p1);i<=(p2);++i)
#define per(i,p1,p2) for(int i=(p1);i>=(p2);--i)

const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;

int qpow(ll x, ll n) {
    ll res = 1;
    while(n) {
        if(n & 1)
            res = res * x % MOD;
        x = x * x % MOD;
        n >>= 1;
    }
    return res;
}

const int MAXN = 2e5;

/*---*/

int n;
ll k;
int a[100005];
ll prefix[100005];
ll suffix[100005];
int a2[100005];
ll prefix2[100005];
ll suffix2[100005];

bool check(ll d) {
    ll cur = 0;
    int i = 1, j = 1;
    while(j + 1 <= n && a[j + 1] - a[i] <= d)
        ++j;
    cur = prefix[i] + suffix[j + 1];
    if(j + 1 <= n)
        cur += (n - j) * (a[j + 1] - (a[i] + d));
    if(cur <= k)
        return true;
    while(j < n) {
        ++i;
        while(j + 1 <= n && a[j + 1] - a[i] <= d)
            ++j;
        cur = prefix[i] + suffix[j + 1];
        if(j + 1 <= n)
            cur += (n - j) * (a[j + 1] - (a[i] + d));
        if(cur <= k)
            return true;
    }
    cur = 0;
    i = 1, j = 1;
    while(j + 1 <= n && a2[i] - a2[j + 1] <= d)
        ++j;
    cur = prefix2[i] + suffix2[j + 1];
    if(j + 1 <= n)
        cur += (n - j) * ((a2[i] - d) - a2[j + 1]);
    if(cur <= k)
        return true;
    while(j < n) {
        ++i;
        while(j + 1 <= n && a2[i] - a2[j + 1] <= d)
            ++j;
        cur = prefix2[i] + suffix2[j + 1];
        if(j + 1 <= n)
            cur += (n - j) * ((a2[i] - d) - a2[j + 1]);
        if(cur <= k)
            return true;
    }
    return false;
}

ll bs() {
    ll L = 0, R = a[n] - a[1];
    while(1) {
        ll M = (L + R) >> 1;
        if(L == M) {
            if(check(L))
                return L;
            return R;
        }
        if(check(M))
            R = M;
        else
            L = M + 1;
    }
}

void test_case() {
    scanf("%d%lld", &n, &k);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; ++i)
        prefix[i] = prefix[i - 1] + 1ll * (i - 1) * (a[i] - a[i - 1]);
    for(int i = n; i >= 1; --i)
        suffix[i] = suffix[i + 1] + 1ll * (n - i) * (a[i + 1] - a[i]);
    for(int i = 1; i <= n; ++i)
        a2[i] = a[n - i + 1];
    for(int i = 1; i <= n; ++i)
        prefix2[i] = prefix2[i - 1] + 1ll * (i - 1) * (a2[i - 1] - a2[i]);
    for(int i = n; i >= 1; --i)
        suffix2[i] = suffix2[i + 1] + 1ll * (n - i) * (a2[i] - a2[i + 1]);
    printf("%lld\n", bs());
}

/*---*/

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();
    }
}

/*
    1. 小数据问题退化:
        输入为0或1会不会有特殊情况?其他的比如最小环要有三个点,强连通分量缩到最后一个点等等。
    2. 内存:
        内存的空间有没有开够?有时有反向边,有时有额外新加的边。线段树开了4倍了吗?
        可持久化数据结构会不会内存溢出?多组数据时vector会不会翻车?字符大小写有考虑吗?
        多组数据有进行初始化吗?memset会不会翻车?
    3. 算术溢出:
        乘法上溢出了?忘记取模了?输入输出用了%d?让无符号数越到负数了?
    4. 习惯:
        函数都有指定返回值吗?返回值非void函数的每一个分支都有显式的返回值吗?确定不会进入的分支可以assert一把。
        Yes和No有没有反,大小写有没有错?有没有搞错n和m?离散化之后的cn有没有错?换行和空格要怎么整?priority要把符号反过来。
        id和aid是不是没有考虑,是不是弄反了?操作的是换名之后的,输出时应该是换名之前的。
    5. 其他bug:
        基环树是树加环,不是链加环。
*/

的确直接贪心推进边界是最简单的,每次移动要么合并多一个元素,要么把k消去一大截,使得不再能推进这个边界。

int n;
ll k;
int a[100005];

void test_case() {
    scanf("%d%lld", &n, &k);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
    }
    sort(a + 1, a + 1 + n);
    int L = 1, R = n;
    int cntL = 1, cntR = 1;
    int curL = a[1], curR = a[n];
    while(L + 1 <= R && a[L + 1] == curL) {
        ++cntL;
        ++L;
    }
    while(R - 1 >= L && a[R - 1] == curR) {
        ++cntR;
        --R;
    }
    if(L == R) {
        puts("0");
        return;
    }
    while(L < R && k >= min(cntL, cntR)) {
        if(cntL <= cntR) {
            int d = min(k / cntL, 1ll * a[L + 1] - a[L]);
            curL += d;
            k -= 1ll * d * cntL;
            while(L + 1 <= R && a[L + 1] == curL) {
                ++cntL;
                ++L;
            }
        } else {
            int d = min(k / cntR, 1ll * a[R] - a[R - 1]);
            curR -= d;
            k -= 1ll * d * cntR;
            while(R - 1 >= L && a[R - 1] == curR) {
                ++cntR;
                --R;
            }
        }
    }
    printf("%d\n", curR - curL);
}

F - Chips

题意:围成一个环的n颗黑白球,每个球每个时刻后会发生变化:当且仅当其邻居都和它异色时自己会变色。所有的球是同时变色的。求k时刻后这个环的样子。

题解:假如是偶数个球且黑白间隔,那么只需要判断k的奇偶性就可以了。否则连续的两个以上同色球是不会变化的,会变化的只是若干条链,我称为“异色链”,比如:

BWBWBWB

异色链就是

WBWBW

注意到每时刻异色链的长度会缩短2。直接贪心。

不知道这样写对不对:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
#define rep(i,p1,p2) for(int i=(p1);i<=(p2);++i)
#define per(i,p1,p2) for(int i=(p1);i>=(p2);--i)

const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;

int qpow(ll x, ll n) {
    ll res = 1;
    while(n) {
        if(n & 1)
            res = res * x % MOD;
        x = x * x % MOD;
        n >>= 1;
    }
    return res;
}

const int MAXN = 2e5;

/*---*/

int n, k;
char a[600005];
char b[600005];

void test_case() {
    scanf("%d%lld%s", &n, &k, a + 1);
    bool suc = 1;
    for(int i = 2; i <= n; ++i) {
        if(a[i] != a[i - 1]) {
            suc = 0;
            break;
        }
    }
    if(suc) {
        puts(a + 1);
        return;
    }
    if(!(n & 1)) {
        bool suc = 1;
        for(int i = 2; i <= n; ++i) {
            if(a[i] == a[i - 1]) {
                suc = 0;
                break;
            }
        }
        if(suc) {
            if(k & 1) {
                for(int i = 1; i <= n; ++i)
                    printf("%c", "BW"[a[i] == 'B']);
                putchar('\n');
                return;
            }
            for(int i = 1; i <= n; ++i)
                printf("%c", "BW"[a[i] == 'W']);
            putchar('\n');
            return;
        }
    }
    for(int i = 1; i <= n; ++i)
        a[i + n + n] = a[i + n] = a[i];
    int L, beg;
    for(int i = 2;; ++i) {
        if(a[i] == a[i - 1]) {
            L = i - 1;
            beg = i - 1;
            break;
        }
    }
    int R = L + n - 1;
    //printf("L=%d R=%d\n", L - beg + 1, R - beg + 1);
    while(a[R] == a[L])
        --R;
    while(a[L + 1] == a[L])
        ++L;
    ++L;
    /*此时[L,R]就是链的两端*/
    while(L <= R) {
        //printf("L=%d R=%d\n", L - beg + 1, R - beg + 1);
        int M = L;
        for(int i = L + 1; i <= R; ++i) {
            if(a[i] != a[i - 1])
                M = i;
            else {
                M = i - 2;
                break;
            }
        }
        if(M >= L) {
            /*此时[L,M]为第一个异色链*/
            if((M - L + 1) % 2 == 0) {
                //printf("L=%d M=%d\n", L - beg + 1, M - beg + 1);
                //偶数
                int d = min(k, (M - L + 1) / 2);
                int L2 = L, M2 = M;;
                for(int i = L, u = 1; u <= d; ++u, ++i) {
                    a[i] = a[i - 1];
                    L2 = i + 1;
                }
                for(int i = M, u = 1; u <= d; ++u, --i) {
                    a[i] = a[i + 1];
                    M2 = i - 1;
                }
                if(k & 1) {
                    for(int i = L2; i <= M2; ++i)
                        a[i] = ("BW"[a[i] == 'B']);
                }
            } else {
                //printf("L=%d M=%d\n", L - beg + 1, M - beg + 1);
                int d = min(k, (M - L + 1 + 1) / 2);
                int L2 = L, M2 = M;;
                for(int i = L, u = 1; u <= d; ++u, ++i) {
                    a[i] = a[i - 1];
                    L2 = i + 1;
                }
                for(int i = M, u = 1; u <= d; ++u, --i) {
                    a[i] = a[i + 1];
                    M2 = i - 1;
                }
                if(k & 1) {
                    for(int i = L2; i <= M2; ++i)
                        a[i] = ("BW"[a[i] == 'B']);
                }
            }
            L = M + 1;
        } else {
            ++L;
        }
        while(L + 1 <= R && a[L + 1] == a[L])
            ++L;
        ++L;
        /*for(int i = beg, u = 1; u <= n; ++u, ++i)
            putchar(a[i]);
        putchar('\n');*/
    }
    for(int i = beg, u = 1; u <= n; ++u, ++i) {
        b[i] = a[i];
        if(i - n >= 1)
            b[i - n] = a[i];
    }
    for(int i = 1; i <= n; ++i)
        putchar(b[i]);
    putchar('\n');
}

/*---*/

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();
    }
}

/*
    1. 小数据问题退化:
        输入为0或1会不会有特殊情况?其他的比如最小环要有三个点,强连通分量缩到最后一个点等等。
    2. 内存:
        内存的空间有没有开够?有时有反向边,有时有额外新加的边。线段树开了4倍了吗?
        可持久化数据结构会不会内存溢出?多组数据时vector会不会翻车?字符大小写有考虑吗?
        多组数据有进行初始化吗?memset会不会翻车?
    3. 算术溢出:
        乘法上溢出了?忘记取模了?输入输出用了%d?让无符号数越到负数了?
    4. 习惯:
        函数都有指定返回值吗?返回值非void函数的每一个分支都有显式的返回值吗?确定不会进入的分支可以assert一把。
        Yes和No有没有反,大小写有没有错?有没有搞错n和m?离散化之后的cn有没有错?换行和空格要怎么整?priority要把符号反过来。
        id和aid是不是没有考虑,是不是弄反了?操作的是换名之后的,输出时应该是换名之前的。
    5. 其他bug:
        基环树是树加环,不是链加环。
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
#define rep(i,p1,p2) for(int i=(p1);i<=(p2);++i)
#define per(i,p1,p2) for(int i=(p1);i>=(p2);--i)

const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;

int qpow(ll x, ll n) {
    ll res = 1;
    while(n) {
        if(n & 1)
            res = res * x % MOD;
        x = x * x % MOD;
        n >>= 1;
    }
    return res;
}

const int MAXN = 2e5;

/*---*/

int n, k;
char a[400005];

void test_case() {
    scanf("%d%d%s", &n, &k, a + 1);
    bool suc = 1;
    for(int i = 2; i <= n; ++i) {
        if(a[i] != a[i - 1]) {
            suc = 0;
            break;
        }
    }
    if(suc) {
        puts(a + 1);
        return;
    }
    if(n % 2 == 0) {
        bool suc = 1;
        for(int i = 2; i <= n; ++i) {
            if(a[i] == a[i - 1]) {
                suc = 0;
                break;
            }
        }
        if(suc) {
            if(k & 1) {
                for(int i = 1; i <= n; ++i)
                    printf("%c", "BW"[a[i] == 'B']);
                putchar('\n');
                return;
            }
            for(int i = 1; i <= n; ++i)
                printf("%c", "BW"[a[i] == 'W']);
            putchar('\n');
            return;
        }
    }
    for(int i = 1; i <= n; ++i)
        a[i + n] = a[i];
    int L, beg;
    for(int i = 2;; ++i) {
        if(a[i] == a[i - 1]) {
            L = i - 1;
            beg = i - 1;
            break;
        }
    }
    int R = L + n - 1;
    while(a[R] == a[L])
        --R;
    while(a[L + 1] == a[L])
        ++L;
    ++L;
    /*此时[L,R]就是链的两端*/
    while(L <= R) {
        int M = R;
        for(int i = L + 1; i <= R; ++i) {
            if(a[i] == a[i - 1]) {
                M = i - 2;
                break;
            }
        }
        if(M >= L) {
            /*此时[L,M]为第一个异色链*/
            int d;
            if((M - L + 1) % 2 == 0)
                d = min(k, (M - L + 1) / 2);
            else
                d = min(k, (M - L + 1 + 1) / 2);
            int L2 = L, M2 = M;;
            for(int i = L, u = 1; u <= d; ++u, ++i) {
                a[i] = a[i - 1];
                L2 = i + 1;
            }
            for(int i = M, u = 1; u <= d; ++u, --i) {
                a[i] = a[i + 1];
                M2 = i - 1;
            }
            if(k & 1) {
                for(int i = L2; i <= M2; ++i)
                    a[i] = ("BW"[a[i] == 'B']);
            }
            L = M + 1;
        } else {
            /*此时没有异色链*/
            ++L;
        }
        while(L + 1 <= R && a[L + 1] == a[L])
            ++L;
        ++L;
    }
    for(int i = beg, u = 1; u <= n; ++u, ++i) {
        if(i - n >= 1)
            a[i - n] = a[i];
    }
    for(int i = 1; i <= n; ++i)
        putchar(a[i]);
    putchar('\n');
}

/*---*/

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();
    }
}

/*
    1. 小数据问题退化:
        输入为0或1会不会有特殊情况?其他的比如最小环要有三个点,强连通分量缩到最后一个点等等。
    2. 内存:
        内存的空间有没有开够?有时有反向边,有时有额外新加的边。线段树开了4倍了吗?
        可持久化数据结构会不会内存溢出?多组数据时vector会不会翻车?字符大小写有考虑吗?
        多组数据有进行初始化吗?memset会不会翻车?
    3. 算术溢出:
        乘法上溢出了?忘记取模了?输入输出用了%d?让无符号数越到负数了?
    4. 习惯:
        函数都有指定返回值吗?返回值非void函数的每一个分支都有显式的返回值吗?确定不会进入的分支可以assert一把。
        Yes和No有没有反,大小写有没有错?有没有搞错n和m?离散化之后的cn有没有错?换行和空格要怎么整?priority要把符号反过来。
        id和aid是不是没有考虑,是不是弄反了?操作的是换名之后的,输出时应该是换名之前的。
    5. 其他bug:
        基环树是树加环,不是链加环。
*/

G - Running in Pairs

题意:给两个排列,调整他们的位置使得他们对应位置的最大值的和最大,且这个值不能超过k。

题解:最小的肯定是同向,最大的肯定是反向。但是怎么调整呢?一开始全部设为同向,然后发现首尾对称位置交换会使得答案变大,且能最节约这个最大的数字,当序列长度为奇数时,每次交换得到都是偶数,且这个收益逐渐减少到2,最后可能可以进行一次临位交换有可能把答案扩大1(仅当本身中部是正序时)。当序列的长度为偶数时,首尾交换每次都是奇数,且收益减少到1,显然这样可以凑出任意一个自然数。(错,不能构造出2,估计很多人也要FST在这里了,2要通过其他方法构造出来)

1 2 3 4
1 2 3 4

偶数个的情况,截取中间的四个如上,因为外面已经处理过+5了,所以现在只需要考虑<=4的情况。
要构造4,和上面说的一样,+3+1:swap(1,4),swap(2,3)
要构造3,和上面说的一样,+3:swap(1,4)
要构造2,+2:swap(2,4)
要构造1,和上面说的一样,+1:swap(2,3)

大的思路是对的,细节不行,以后在边界(这里的边界是中部数据较小时)要格外注意。

int n;
ll k;

int a[1000005];
int b[1000005];

void test_case() {
    scanf("%d%lld", &n, &k);
    if(n == 1) {
        printf("1\n1\n1\n");
        return;
    }
    if(n == 2) {
        if(k < 3)
            printf("-1\n");
        else if(k == 3)
            printf("3\n1 2\n1 2\n");
        else
            printf("4\n1 2\n2 1\n");
        return;
    }
    ll sum = 0;
    for(int i = 1; i <= n; ++i) {
        a[i] = i;
        b[i] = i;
        sum += i;
    }
    if(k < sum) {
        puts("-1");
        return;
    }

    int mid = (n + 1) / 2;
    if(n % 2 == 1) {
        for(int i = 1; i <= n / 2; ++i) {
            ll d = (a[n - i + 1] - a[i]);
            if(sum + d <= k) {
                swap(b[i], b[n - i + 1]);
                sum += d;
            }
        }
        if(sum + 1 <= k) {
            if(b[mid - 1] < b[mid]) {
                swap(a[mid - 1], a[mid]);
                ++sum;
            }
        }
    } else {
        for(int i = 1; i <= (n - 4) / 2; ++i) {
            ll d = (a[n - i + 1] - a[i]);
            if(sum + d <= k) {
                swap(b[i], b[n - i + 1]);
                sum += d;
            }
        }
        if(sum + 4 <= k) {
            swap(b[mid], b[mid + 1]);
            swap(b[mid - 1], b[mid + 2]);
            sum += 4;
        } else if(sum + 3 <= k) {
            swap(b[mid - 1], b[mid + 2]);
            sum += 3;
        } else if(sum + 2 <= k) {
            swap(b[mid], b[mid + 2]);
            sum += 2;
        } else if(sum + 1 <= k) {
            swap(b[mid], b[mid + 1]);
            sum += 1;
        }
    }
    ll tsum = 0;
    for(int i = 1; i <= n; ++i)
        tsum += max(a[i], b[i]);
    printf("%lld\n", tsum);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", a[i], " \n"[i == n]);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", b[i], " \n"[i == n]);
    assert(tsum == sum);
}
posted @ 2019-11-18 21:29  KisekiPurin2019  阅读(141)  评论(0编辑  收藏  举报