Codeforces Global Round 3

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

当年我不会做这个B题,却把CD过了。这个E题想错了。

A - Another One Bites The Dust

题意:有x个"a",y个"b",z个"ab",求最长的一个连续两个字符都不相同的字符串。

题解:肯定是先把z全部用了,易知'a'的数量和'b'的数量的相差不能超过1。

*B - Born This Way

这个题感觉不简单,没想到是1700难度的。

题意:要从A坐飞机中转B再到达C。A有n种航班飞到B,起飞时间分别是ai,飞行时间都是ta。B有m种航班飞到C,起飞时间分别是bj,飞行时间都是tb。取消不超过k种航班,使得最早的到达C的时间最迟。

题解:一开始想着贪心,但是怎么知道要使得这个B航班失效,是直接把他取消还是把能到他的A航班都取消呢?这个不能贪心。但是观察一段时间之后会发现,A航班总是取消最早的连续x种是最好的,因为假如也是取消x种,假如不是最早的连续x种,那么后面的就白白浪费取消的次数。然后又可以惊人的发现,枚举x之后,再取消第一个没有被取消的A航班能接上的接下来的k-x种B航班,就是最优的。也就是说是贪心取消掉最早的A航班,然后贪心取消掉剩下的有用的B航班里面最早的。

枚举这个x,然后双指针推一下。

不要溢出。

ll a[200005];
ll b[200005];
 
void TestCase() {
    int n, m, k;
    ll ta, tb;
    scanf("%d%d%lld%lld%d", &n, &m, &ta, &tb, &k);
    for(int i = 1; i <= n; ++i)
        scanf("%lld", &a[i]);
    for(int j = 1; j <= m; ++j)
        scanf("%lld", &b[j]);
    if(k >= n || k >= m) {
        puts("-1");
        return;
    }
    ll ans = 0;
    int curj = 1;
    //由于下面是枚举取消A航班的数量,所以上界是k+1,对应全部取消,易知a[k+1]一定存在
    for(int i = 1; i <= k + 1; ++i) {
        //取消掉前面的前i-1种A航班
        while(curj <= m && a[i] + ta > b[curj]) {
            //双指针找到第i种A航班能使用的第1个B航班,记为curj
            ++curj;
        }
        int t = k - (i - 1);
        //若剩余的B航班可以全部取消,输出-1
        if((m - curj + 1) <= t) {
            puts("-1");
            return;
        }
        //取消[curj,curj+t-1]的B航班
        ans = max(ans, b[curj + t] + tb);
    }
    printf("%lld
", ans);
 
}

C - Crazy Diamond

这个题也比较有想法。

题意:给一个n个数的排列,n是偶数,使用不超过5n次交换,给这个数组排序,要求每次交换的时候,选择的两个下标(i,j),满足2|i-j|>=n,换言之就是两个要隔得足够远,至少要隔半个数列的长度。

题解:这样看见“半个数列的长度”,就想到可以充分利用第1个元素和第n个元素来搞事,易知前半区间[1,n/2]的都可以和第n个元素交换,后半区间[n/2+1,n]的都可以和第1个元素交换。

先写一个交换函数,这样就不怕搞错了。

void myswap(int x, int y) {
    swap(a[x], a[y]);
    swap(pos[a[x]], pos[a[y]]);
    ans[++atop] = {x, y};
}

每次调整第i个元素的时候,都满足前i-1个元素复位。

那么设数字i的位置为x,若2|x-i|>=n,就可以直接交换。

否则就要借助一些外力了。

以n=8为例:

1、若i<=n/2,且x>n/2:

由于他们离得比较近,所以i不会是1,x也不会是n

1.i.x..n

先交换(x,1)

x.i.1..n

再交换(1,n)

n.i.1..x

再交换(i,n)

n.x.1..i

再交换(x,1)

1.x.n..i

这样就完成了任务,至于n和i是怎么样的,就留给后面的迭代自己处理。

2、若i<=n/2,且x<=n/2:

1.ix...n

先交换(i,n)

1.nx...i

再交换(x,n)

1.ni...x

再交换(i,n)

1.xi...n

又完成了任务。

否则,两者都必定在右半区间,把上面的第2种情况反过来就可。

int n;
int a[300005];
int pos[300005];
 
pii ans[1500005];
int atop;
 
void myswap(int x, int y) {
    swap(a[x], a[y]);
    swap(pos[a[x]], pos[a[y]]);
    ans[++atop] = {x, y};
}
 
void solve(int i) {
    if(a[i] == i)
        return;
    int x = pos[i];
    if(2 * (x - i) >= n) {
        myswap(x, i);
        return;
    }
    if(i <= n / 2) {
        if(x > n / 2) {
            myswap(x, 1);
            myswap(1, n);
            myswap(n, i);
            myswap(x, 1);
            return;
        } else {
            myswap(i, n);
            myswap(x, n);
            myswap(i, n);
            return;
        }
    } else {
        myswap(i, 1);
        myswap(x, 1);
        myswap(i, 1);
        return;
    }
}
 
void TestCase() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        pos[a[i]] = i;
    }
    atop = 0;
    for(int i = 1; i <= n; ++i)
        solve(i);
    printf("%d
", atop);
    for(int i = 1; i <= atop; ++i)
        printf("%d %d
", ans[i].first, ans[i].second);
    return;
}

*D - Dirty Deeds Done Dirt Cheap

这道题这么难,我当年居然会?

题意:给n对数字<ai,bi>,每个数字都是不同的,且正好是[1,2n],选出最长的一个集合,使得他们可以排列成一个“好串”。一个“好串”,是符号为"a1a2a3a4<b4"或者"a1>b1b2b3b4"的串,也就是这些符号是反过来的。

题解:可以确定每对数字的符号是'<'还是'>',显然不同种符号是不可以接在一起的。那么就先按内部符号分成两类,然后我惊人地发现,这些可以全部排在一起!比如都是'<'号的,中间都必须用'>'号连接,那么假如把这些按右边的数字bi的大小从大到小排序,那么b1>b2,而a2<b2,所以一定是能接上的,对称的情况同理。

struct Node {
    int id;
    int l, r;
} nd[300005];
 
pii p1[300005],p2[300005];
 
void TestCase() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        nd[i].id = i;
        scanf("%d%d", &nd[i].l, &nd[i].r);
    }
    int m1 = 0, m2 = 0;
    for(int i = 1; i <= n; ++i) {
        if(nd[i].l < nd[i].r) {
            ++m1;
            p1[m1].first = -nd[i].r;
            p1[m1].second = nd[i].id;
        } else {
            ++m2;
            p2[m2].first = nd[i].r;
            p2[m2].second = nd[i].id;
        }
    }
    if(m1 >= m2) {
        sort(p1 + 1, p1 + 1 + m1);
        printf("%d
", m1);
        for(int i = 1; i <= m1; ++i)
            printf("%d%c", p1[i].second, " 
"[i == m1]);
    }
    else{
        sort(p2 + 1, p2 + 1 + m2);
        printf("%d
", m2);
        for(int i = 1; i <= m2; ++i)
            printf("%d%c", p2[i].second, " 
"[i == m2]);
    }
}

*E - Earth Wind and Fire

题意:给n个石头,每次可以选两个石头,让他们彼此靠近一定距离,准确的说,选择两个(i,j),下标为pi,pj且pi<pj,然后选择一个d满足0<=2d<=pj-pi,再换成pi+d和pj-d,也就是挨近了一点。要求使用不超过5n次操作从石头序列s构造出石头序列t。注意石头序列t是无序的。

题解:注意到操作不会改变所有元素的和,所以要是s序列的和与t序列的和不一样就是NO。然后假如某次t序列的无法构造,也是NO。一开始觉得是每次取s序列两端的出来向中间靠,直到碰到第一个t序列石头t为止,这样每次可以至少消除一个t序列的石头。

写出来是这样的:

map<int, vector<int> > MAP;
deque<int> DEQUE;
 
int t[300005];
struct Ans {
    int i, j, d;
    Ans(int i = 0, int j = 0, int d = 0): i(i), j(j), d(d) {}
} ans[300005];
int atop;
 
void TestCase() {
    int n;
    scanf("%d", &n);
    ll sum1 = 0;
    for(int i = 1; i <= n; ++i) {
        int x;
        scanf("%d", &x);
        MAP[x].push_back(i);
        sum1 += x;
    }
    ll sum2 = 0;
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &t[i]);
        sum2 += t[i];
    }
    if(sum1 != sum2) {
        puts("NO");
        return;
    }
    sort(t + 1, t + 1 + n);
    for(int i = 1; i <= n; ++i)
        DEQUE.push_back(t[i]);
    while(DEQUE.size()) {
        int pos1 = MAP.begin()->first;
        int id1 = MAP.begin()->second.back();
 
        int ypos1 = DEQUE.front();
 
        if(pos1 == ypos1) {
            MAP.begin()->second.pop_back();
            DEQUE.pop_front();
            if(MAP.begin()->second.empty())
                MAP.erase(pos1);
            continue;
        }
 
        int pos2 = MAP.rbegin()->first;
        int id2 = MAP.rbegin()->second.back();
 
        int ypos2 = DEQUE.back();
 
        if(pos2 == ypos2) {
            MAP.rbegin()->second.pop_back();
            DEQUE.pop_back();
            if(MAP.rbegin()->second.empty())
                MAP.erase(pos2);
            continue;
        }
 
        MAP.begin()->second.pop_back();
        MAP.rbegin()->second.pop_back();
 
        if(MAP.begin()->second.empty())
            MAP.erase(pos1);
        if(MAP.rbegin()->second.empty())
            MAP.erase(pos2);
 
        if(pos1 > ypos1 || pos2 < ypos2) {
            puts("NO");
            return;
        }
        int dis = min(ypos1 - pos1, pos2 - ypos2);
        pos1 += dis;
        pos2 -= dis;
        if(pos1 == ypos1) {
            DEQUE.pop_front();
            if(pos2 == ypos2)
                DEQUE.pop_back();
            else
                MAP[pos2].push_back(id2);
        } else {
            assert(pos2 == ypos2);
            DEQUE.pop_back();
            MAP[pos1].push_back(id1);
        }
        ans[++atop] = Ans(id1, id2, dis);
    }
    puts("YES");
    printf("%d
", atop);
    for(int i = 1; i <= atop; ++i)
        printf("%d %d %d
", ans[i].i, ans[i].j, ans[i].d);
    return;
}

但是这样是错的,比如下面的例子:

4
1 3 5 7
2 2 6 6

假如按照上面的算法,会先变成(1,7)->(2,6):

2 3 5 6

然后报告NO。

但是实际上可以用(1,3)->(2,2),(5,7)->(6,6)。

另一种思路是,先给s序列和t序列排序,那么他们就是对应的一一对应的移动关系,这样每个石头有可能是需要左移,也有可能是需要右移。但是第一个石头绝对是要右移,那么就找到第一个左移的石头,移动min(石头2左移的距离,石头1右移的距离),这样至少会把一个石头归位。易知需要左移的总和和需要右移的总和是相等的,所以是一定可以构造的。

pii s[300005];
int t[300005];

struct Ans {
    int i, j, d;
    Ans(int i = 0, int j = 0, int d = 0): i(i), j(j), d(d) {}
} ans[300005];
int atop;

void AddAns(int i, int j, int d) {
    s[i].first += d;
    s[j].first -= d;
    ans[++atop] = Ans(s[i].second, s[j].second, d);
}

void TestCase() {
    int n;
    scanf("%d", &n);
    ll sums = 0;
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &s[i].first);
        s[i].second = i;
        sums += s[i].first;
    }
    ll sumt = 0;
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &t[i]);
        sumt += t[i];
    }
    if(sums != sumt) {
        puts("NO");
        return;
    }
    sort(s + 1, s + 1 + n);
    sort(t + 1, t + 1 + n);
    atop = 0;
    for(int i = 1, j = 1; i <= n; ++i) {
        if(s[i].first > t[i]) {
            puts("NO");
            return;
        }
        if(s[i].first == t[i])
            continue;
        while(s[i].first < t[i]) {
            int d1 = t[i] - s[i].first;
            j = max(j, i + 1);
            while(j <= n && s[j].first <= t[j])
                ++j;
            if(j > n) {
                puts("NO");
                return;
            }
            int d2 = s[j].first - t[j];
            AddAns(i, j, min(d1, d2));
        }
    }
    puts("YES");
    printf("%d
", atop);
    for(int i = 1; i <= atop; ++i)
        printf("%d %d %d
", ans[i].i, ans[i].j, ans[i].d);
}
原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12572742.html