Codeforces Round #377 (Div. 2)

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

A - Buy a Shovel

题意:有无穷枚10元硬币和1枚r元硬币,且r是[1,9]。每个物品的单价是x元,求最少买多少件不用找钱。

题解:记得区分用r元和不用r元两种情况,分别对应模10的余数为r或者为0。现在枚举不超过10个就可以出结果。

B - Cormen --- The Best Friend Of a Man

题意:宠物狗每连续两天都需要总共走至少k步,注意对于[0,1]和[n,n+1]没有这个要求。给出一个数列表示第几天走几步,增加最少的步使得[1,2],[2,3]...[n-1,n]都满足每连续两天都需要总共走至少k步。

题解:贪心往右边的一天放。注意最后一天。

C - Sanatorium

题意:某人在食堂待了连续的一段时间,而每一天都依次吃早餐b、晚餐d、宵夜s,而他有可能会漏掉其中的一些餐。已知三种餐吃的次数,求最少漏了多少餐。

题解:可以分多种情况讨论,得到一个贪心:

假设他是从b开始吃,最后一餐吃b,则b会比另外两种多1。

假设他是从b开始吃,最后一餐吃d,则b和d会比s多1。

假设……

最后会发现一个规律,要把最少的两种都配到最多比最多的一种少1,而和他们的顺序无关(三种是完全对称的)。

*D - Exams

挺有意思的一道题。

题意:一段连续的日子,每个日子可能会有恰好一门课的Exam,或者没有任何Exam。问这段日子是否可以成功通过所有课的考试。注意每门课可能有多次Exam,只需要选一门完成。要是不参加当天的考试,则可以用来复习。第j门课在考试前需要aj天复习,问最短的通过所有考试的时间,或说明无解。

题解:一开始还在想怎么用堆(延迟决策贪心)来贪心,但是显然有个二分的算法,二分天数x,则每门课肯定选x及其之前的最后一次考试来考,这个可以O(n)得到,然后必定是贪心,到了选择的一门课的时候就要尽可能分配已有的复习资源给它,不够分配则GG。

int n, m;
int d[100005];
int a[100005];

pii lst[100005];

bool check(int len) {
    for(int i = 1; i <= m; ++i)
        lst[i] = {0, a[i]};
    for(int i = 1; i <= len; ++i)
        lst[d[i]].first = i;
    for(int i = 1; i <= m; ++i) {
        if(lst[i].first == 0)
            return 0;
    }
    sort(lst + 1, lst + 1 + m);
    int cnt0 = lst[1].first - 1;
    for(int i = 1; i <= m; ++i) {
        if(cnt0 < lst[i].second)
            return 0;
        else {
            if(i == m)
                return 1;
            cnt0 -= lst[i].second;
            cnt0 += (lst[i + 1].first - lst[i].first - 1);
        }
    }
    return 0;
}

int LB;
int bs() {
    int L = LB, R = n;
    while(1) {
        int M = (L + R) >> 1;
        if(L == M) {
            if(check(L))
                return L;
            if(check(R))
                return R;
            return -1;
        }
        if(check(M))
            R = M;
        else
            L = M + 1;
    }
}

bool vis[100005];

void test_case() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &d[i]);
    int cnt = 0;
    LB = -1;
    for(int i = 1; i <= n; ++i) {
        if(!vis[d[i]]) {
            vis[d[i]] = 1;
            ++cnt;
            if(cnt == m) {
                LB = i;
                break;
            }
        }
    }
    if(LB == -1) {
        puts("-1");
        return;
    }
    for(int i = 1; i <= m; ++i)
        scanf("%d", &a[i]);
    printf("%d\n", bs());
}

*E - Sockets

题意:有 \(n(1\leq n \leq 2\cdot 10^5)\) 电脑, \(m(1\leq m \leq 2\cdot 10^5)\) 个插座,其中每个电脑和插座都有自己唯一的功率。然后可以花一个适配器把一个插座的功率变成它的一半的上整(为什么是上整,不是很明白)。匹配尽可能多的电脑,在此前提上使用尽可能少的适配器。

题解:比赛时有个假算法,感觉比题目要求的复杂度略高。把所有电脑和插座都放在map里,键是功率,值是序号数组。显然在可以匹配的时候就匹配肯定是最优的,因为留着电脑只会在下次匹配多用适配器或者直接失配。所以每次把插座的值都减半就可以了,总共的复杂度应该是 \(O(nlognlogMaxPower)\) ,写了一发居然MLE了,说明map开内存实在玄学,所以后面改成值就不保存序号数组了,改为保存代表序号数组的地址。

但是按道理把这俩直接排序之后,可以直接双指针扫描完成匹配,然后再扫一遍完成减半,还不需要再排序,所以总复杂是 \(O(nlogMaxPower+nlogn)\)

内存和时间都差点爆了。

stack<int> nxtA[200005];
stack<int> nxtB[200005];
map<int, int> A;
map<int, int> B;
map<int, int> TB;

int C[200005];
int D[200005];

void test_case() {
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1, a; i <= n; ++i) {
        scanf("%d", &a);
        if(!A.count(a))
            A[a] = i;
        nxtA[A[a]].push(i);
    }
    for(int j = 1, b; j <= m; ++j) {
        scanf("%d", &b);
        if(!B.count(b))
            B[b] = j;
        nxtB[B[b]].push(j);
    }
    int cnt = 0, sum = 0;
    int cost = 0;
    int t = 31;
    while(t--) {
        for(auto &i : B) {
            auto it = A.find(i.first);
            if(it == A.end())
                continue;
            else {
                stack<int> &sB = nxtB[i.second];
                stack<int> &sA = nxtA[it->second];
                while(sA.size() && sB.size()) {
                    int u = sB.top();
                    int v = sA.top();
                    C[u] = cost;
                    D[v] = u;
                    sum += cost;
                    ++cnt;
                    sB.pop();
                    sA.pop();
                }
            }
        }
        ++cost;
        for(auto &i : B) {
            if(nxtB[i.second].size() == 0)
                continue;
            auto it = TB.find((i.first + 1) / 2);
            if(it == TB.end())
                TB[(i.first + 1) / 2] = i.second;
            else {
                stack<int> &sB = nxtB[i.second];
                int v = it->second;
                while(sB.size()) {
                    nxtB[v].push(sB.top());
                    sB.pop();
                }
            }
        }
        B.swap(TB);
        TB.clear();
    }
    printf("%d %d\n", cnt, sum);
    for(int j = 1; j <= m; ++j)
        printf("%d%c", C[j], " \n"[j == m]);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", D[i], " \n"[i == n]);
}

重新写一个好点的。突然发现反正复杂度都是对的,甚至没有必要删除元素,直接标记为删除就可以了。

int n, m;
struct AB {
    int val;
    int idx;
    int vis;
} A[200005], B[200005];

bool cmpval(const AB &c1, const AB &c2) {
    return c1.val < c2.val;
}

bool cmpidx(const AB &c1, const AB &c2) {
    return c1.idx < c2.idx;
}

void test_case() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &A[i].val);
        A[i].idx = i;
        A[i].vis = 0;
    }
    for(int j = 1; j <= m; ++j) {
        scanf("%d", &B[j].val);
        B[j].idx = j;
        B[j].vis = 0;
    }
    sort(A + 1, A + 1 + n, cmpval);
    sort(B + 1, B + 1 + m, cmpval);
    int suc = 0, sum = 0, cnt = 0;
    while(cnt <= 30) {
        int i = 1, j = 1;
        while(1) {
            while(i <= n && A[i].vis != 0)
                ++i;
            while(j <= m && B[j].vis != 0)
                ++j;
            if(i > n || j > m)
                break;
            if(A[i].val == B[j].val) {
                sum += cnt;
                A[i].vis = B[j].idx;
                B[j].vis = A[i].idx;
                B[j].val = cnt;
                ++suc;
                ++i;
                ++j;
            } else {
                if(A[i].val > B[j].val)
                    ++j;
                else
                    ++i;
            }
        }
        for(int j = 1; j <= m; ++j) {
            if(B[j].vis == 0)
                B[j].val = (B[j].val + 1) / 2;
        }
        ++cnt;
    }
    sort(A + 1, A + 1 + n, cmpidx);
    sort(B + 1, B + 1 + m, cmpidx);
    printf("%d %d\n", suc, sum);
    for(int j = 1; j <= m; ++j)
        printf("%d%c", B[j].vis ? B[j].val : 0, " \n"[j == m]);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", A[i].vis, " \n"[i == n]);
}

收获:要注意vis的值不能直接用来记录用了多少个,因为有些是用0个的。需要做一点修改。还有就是注意区分排序后的下标和原下标,题目要的肯定是原下标。

F - Tourist Reform

posted @ 2020-02-09 00:11  KisekiPurin2019  阅读(127)  评论(0编辑  收藏  举报