【题解】Codeforces Manthan, Codefest 18 (rated, Div. 1 + Div. 2) ABCDE

版权声明:欢迎转载评论 https://blog.csdn.net/m0_37809890/article/details/82356741

1037A. Packets

n个硬币,问至少分成多少堆可以用这些堆来组合出1到n的所有硬币数。

分成2的幂次,且n为2的幂次时计数值加一,答案为 log n + 1
可以直接用log2()函数,不会出错。

write((int)log2(read())+1);

1037B. Reach Median

程序:照他说的做
编程:照它说的做

给定n(奇数)个元素的数组和数字k,选择一个元素并加或减1需要付出1的代价,问将数组中位数变为k的最小代价。

最后的结果应当是排序后前n/2个元素小于等于k,第n/2+1个元素等于k,后n/2个元素大于等于k。这样变就好了。


    int n = read(), k =read();
    for(int i=1;i<=n;i++)
        save[i] = read();
    sort(save+1,save+n+1);
    ll ans = 0;
    for(int i=1;i<=n;i++)
    {
        if(i<=n/2)
            ans += max(save[i]-k,0);
        if(i==n/2+1)
            ans += abs(k-save[i]);
        if(i>n/2+1)
            ans += max(k-save[i],0);
    }
    cout << ans << endl;

不应被0开头或1开头的数组所迷惑

1037C. Equalize

给两个长度相同的01串A和B,想要让A变成B,有两种操作:1.交换A[i]与A[j],代价 | i j | 2.翻转A[i],代价1,求最小代价。

操作1仅在A[i]!=B[i]&&A[i+1]!=B[i+1]&&A[i]!=A[i+1]时使用

1038D. Valid BFS?

定义无向图的BFS操作,(使用队列,始终从1号点开始,随机顺序选取邻接节点)。现在给定一棵树和它节点的一个顺序数组,问是否可能是用BFS操作的访问顺序。

首先了解bfs的访问顺序:按深度访问,且每个连续部分的父亲都相同。这题解法很多,我的解法使用了第二个性质。

首先邻接表set存储,然后新建一个队列并把1入队(判断1是否是第一个元素)。
然后从第二个位置开始访问顺序数组,每次查询这个位置的元素是否在队头的邻接集合中,如果在就将这条边删除(两次)并将新元素入队,如果队头的邻接集合为空就出队,如果队头邻接集合不为空且新元素不在其中那么返回错误。
相当于双指针扫了一遍这个数组(队列),每次查询O(logn),总复杂度O(nlogn)

set<int> save[M];
int quen[M];
int main(void)
{
    int n = read();
    for(int i=1;i<n;i++)
    {
        int a = read();
        int b = read();
        save[a].insert(b);
        save[b].insert(a);
    }
    for(int i=1;i<=n;i++)
        quen[i] = read();
    if(quen[1]!=1) return !printf("No\n");

    queue<int> q;
    q.push(1);
    int id = 2;
    while(id<=n)
    {
        if(save[q.front()].size()==0) 
        {
            q.pop();
        }
        else if(save[q.front()].count(quen[id])==0)
        {
            return !printf("No\n");
        }
        else
        {
            save[q.front()].erase(quen[id]);
            save[quen[id]].erase(q.front());
            q.push(quen[id]);
            id++;
        }
    }
    printf("Yes\n");

    return 0;
}

事实上,双指针写while+if总是可读性更高的。

1037E. Trips

给定人数n(2e5),天数m(2e5),阈值k,每一天早上会有两个陌生人成为朋友,朋友不具有传递性。问1到m天分别有多少个人是快乐的,当一个人有k个快乐的朋友时他就是快乐的。

首先只考虑最后一天的情况,假设所有人是快乐的,然后检查所有人并排除朋友数不到k的人,每排除一个人就将他所有朋友的朋友数减一,并且和这个不快乐的人断绝朋友关系再对这个朋友递归地进行检查。
因为每个人最多只会被排除一次,每当一个人被排除时才会检查他的朋友们,所以只执行了O(n+m)次操作,可以用set解决。

然后就可以倒着做,每次删去一条边,然后检查这两个节点即可。
总的复杂度仍为O(nlogn),证明同上。

本题为什么不能正着扫?
因为每次加边都得判断它所连点的所连点的所连点……复杂度非常高,甚至不如全判断一次。

pair<int,int> save[M];
set<int> st[M]; //邻接表
set<int> go;
int n,m,k;
void check(int id)
{
    if(go.count(id) && (int)st[id].size()<k)
    {
        go.erase(id);
        for(auto x : st[id])
        {
            st[x].erase(id);
            check(x);
        }
        st[id].clear();
    }
}
int ans[M];
int main(void)
{
    n = read(), m = read(), k = read();
    for(int i=1;i<=m;i++)
    {
        int a = read(), b = read();
        save[i].first = a, save[i].second = b;
        st[a].insert(b), st[b].insert(a);
    }
    for(int i = 1;i<=n;i++)
        go.insert(i);
    for(int i = 1;i<=n;i++)
        check(i);
    ans[m] = go.size();
    for(int i = m;i>=2;i--)
    {
        int a = save[i].first, b = save[i].second;
        st[a].erase(b), st[b].erase(a);
        check(a), check(b);
        ans[i-1] = go.size();
    }
    for(int i = 1;i<=m;i++)
        printf("%d\n",ans[i] );

    return 0;
}

要输出若干次答案的,可以先考虑最后一次的答案,再回头倒跑或者二分。

1037F. Maximum Reduction

待续

猜你喜欢

转载自blog.csdn.net/m0_37809890/article/details/82356741
今日推荐