Educational Codeforces Round 121 (Rated for Div. 2)思路分享

这次的cf(虽然已经过去了很久...)C题就挂了好久,最后还是没有搞出来.....气死...
(幸好这次网站崩了,没有记rating....)
Educational Codeforces Round 121 (Rated for Div. 2)

A. Equidistant Letters

A题简单构造,我们直接将相同的字母放一起即可。

B. Minor Reduction

B题给定一个很大的数字,要求做一个操作使得相邻的两位替换成两个相加的结果。
首先我们优先考虑它的位数不变,经过简单的思考,发现如果要位数不变的话,(即加起来的和>=10)发现加起来之后这个数一定是减小的(和原来相比)。既然一定要减小,所以我们倒着找,看能不能找到相邻的两个位相加使得他们的和>9.
之后若找不到,说明只能减小一位,但这个情况和上面情况相反,相加之后虽然位数减少了但是数增大了或不变,所以我们直接让第一位和第二位相加即可。

C. Monsters And Spells

首先我们考虑只有一个怪物时,最优的策略就是从k[i]-h[i]+1开始施法,在k[i]时正好以h[i]打败怪物。考虑有两个怪物怎么办,我们设start[i]为怪物i能够正好打败怪物的开始施时间。若start[i+1]>k[i],则两个怪物不影响,我们分别独立的施法打败。若start[i+1]<k[i]的话,这个时候我们将两个怪兽在一个连续施法区间开始打败,而开始的时间就是两个怪兽开始时间的小的值。就按照正常的顺序来做的话,会发现很难的确定如何的合并与统计答案,因为开始时间的无序性,造成很大的麻烦。所以我们不妨将开始时间从小到大进行排序。这样的话,我们只需要知道上一个的开始时间即可。(排序的重要性...)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=110;
int T,n,k[N],h[N],st[N];
vector<pair<int,int>>ve;
inline ll f(int l,int r)
{
    return (ll)(r-l+2)*(r-l+1)/2;
}
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%d",&k[i]);
        for(int i=1;i<=n;++i) scanf("%d",&h[i]);
        ve.clear();
        for(int i=1;i<=n;++i) ve.push_back({k[i]-h[i]+1,k[i]});
        sort(ve.begin(),ve.end());
        ll ans=0;
        int sta=0,pos=0;
        for(int i=0;i<ve.size();++i)
        {
            st[i]=ve[i].first; 
            if(i==0||st[i]>pos) 
            {
                ans+=f(st[i],ve[i].second);
                sta=st[i];pos=ve[i].second;
            }
            else
            {
                if(ve[i].second<pos) continue;
                ans-=f(sta,pos);
                pos=ve[i].second;
                ans+=f(sta,pos);
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
} 
D. Martial Arts Tournament

这个题在没有看题解的情况下,我竟然想出了个线段树优化DP的方法....(妈妈这没点码力是不行的...)
果然我的思维还是不太全面吧....
考虑这个题,首先我们可以先将权值排序计数,这样之后他们之间的数值大小就没有意义了,我们令\(b_i\)表示排名第i的数的个数。我们直接在\(b_i\)上进行划分就不用考虑合不合法的问题了。由于这个题是将这个序列分成三个集合。那么我们肯定是要枚举的其中的集合的。其实我的第一想法就是直接枚举到哪是第一个集合...这根本没有用到2的次幂的条件。既然是枚举集合大小,那么我们根据题意,最优的方法就是枚举答案中的那个2的几次幂,考虑怎么枚举,枚举哪些集合。若枚举中间集合的话,我们还是要考虑划分问题的。所以我们枚举两边集合的大小。之后考虑两边怎么取,这里有个贪心的策略。尽可能的在不超过枚举的集合的情况下,多取。因为我们不够的我们是一定要付出代价的。我们从中间集合将一定数量给左边集合的话,对于中间集合的答案,如果最优解的情况就是必须要那些块的话,你直接付出代价即可,和我们左边因为不够而拿的直接抵消,而且这也有可能使得中间的集合更优。(反正使得中间集合的选择更多了。)或者考虑再最优解的情况下,我们左边拿了a个,最多可以再拿k个,但最优的情况是没有拿,那么我们拿了答案仍不变。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
int n,T,a[N],b[N],ans=1e9,num;
inline int f(int x)
{
    for(int i=0;i<=20;++i) 
    {
        if(x==(1<<i)) return 0;
        if((1<<i)>x) return (1<<i)-x;
    }
}
inline void work(int n1,int n2)
{
    int l=num+1,r=0,sum1=0,sum2=0,sum3=0;
    for(int i=1;i<=num;++i)
    {
        if(sum1+b[i]<=(1<<n1)) sum1+=b[i];
        else {l=i;break;}
    }
    for(int i=num;i>=l;--i)
    {
        if(sum2+b[i]<=(1<<n2)) sum2+=b[i];
        else {r=i;break;}
    }
    for(int i=l;i<=r;++i) sum3+=b[i];
    ans=min(ans,f(sum3)+(1<<n1)-sum1+(1<<n2)-sum2);
}
int main()
{
//    freopen("1.in","r",stdin);
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%d",&a[i]);
        sort(a+1,a+n+1);
        num=0;ans=1e9;
        for(int i=1;i<=n;++i)
        {
            if(a[i]==a[i-1]) b[num]++;
            else b[++num]=1;
        }
        for(int i=0;i<=20;++i)
            for(int j=0;j<=20;++j) work(i,j);
        printf("%d\n",ans);     
    }
    return 0;
} 
posted @ 2022-01-22 21:50  逆天峰  阅读(73)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//