Educational Codeforces Round 42 (Rated for Div. 2) D. Merge Equals (problems 962D) 几种不同的解题思路

版权声明: https://blog.csdn.net/jiangxiewei/article/details/79948270

D. Merge Equals

题目传送门
题目大意就是给一个数组,每次合并左边最小的两个数(左边的合并到右边去),一直合并到无法合并为止


目录:

前几天与以前的ACM的队友们聊天,突然感触良深.于是一起跑去CF做题了(一年没摸过了).
在做完这题之后我发现我的想法效率极低,于是我去问队友们都是怎么实现的,结果发现根据每个人的特性,各自的做法都有些不同.
其中,方案四算是我们认为的最优解了.


一 最low的实现(java)

这也是我的实现,效率真的低而且浪费资源,换一年前的自己根本不敢想象能写出这样的代码,看来真的是太久不思考了,麻木了.你们当笑话看吧
我的提交结果
简单图解
我的做法为每一个[数字]都建了一个优先队列,里面存储了数字的下标.然后从小到大合并,并放到[数字*2]里.
具体反面教材实现如下:

public static void main(String[] args) {

        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        Long[] input = new Long[n];
        TreeMap<Long, PriorityQueue<Integer>> positionMap = new TreeMap<>();
        for (Integer i = 0; i < n; i++) {
            input[i] = scan.nextLong();
            PriorityQueue<Integer> position = positionMap.computeIfAbsent(input[i], aLong -> new PriorityQueue<>());
            position.add(i);
        }

        TreeMap<Integer, Long> sortedMap = new TreeMap<>();
        //每次重新获取迭代器.
        for (Iterator<Long> iter = positionMap.keySet().iterator(); iter.hasNext(); iter = positionMap.keySet().iterator()) {
            //数字
            Long key = iter.next();
            //将相同数据进行合并存入Map,新key为(key+key)
            PriorityQueue<Integer> position = positionMap.get(key);
            //如果大于1,进行合并操作
            if (position.size() > 1) {
                PriorityQueue<Integer> upgradePos = positionMap.get(key + key);
                if (upgradePos == null) {
                    upgradePos = new PriorityQueue<>();
                    positionMap.put(key + key, upgradePos);
                }
                //是否升级到key+key
                boolean upFlag = false;
                //是否需要保留最后一个数(奇数保留)
                boolean remainFlag = (position.size() & 1) == 1;
                while (!position.isEmpty()) {
                    //数字位置
                    Integer pos = position.poll();
                    if (upFlag) {
                        upgradePos.add(pos);
                    }
                    if (remainFlag && position.size() == 1) {
                        break;
                    }
                    upFlag = !upFlag;
                }
            }
            //当前key处理完,存入结果集,将key从原map中移除
            if (!position.isEmpty()) {
                sortedMap.put(position.poll(), key);
            }
            positionMap.remove(key);
        }
        //根据最终下标输出num
        System.out.println(sortedMap.size());
        Iterator<Integer> iterator = sortedMap.keySet().iterator();
        while (iterator.hasNext()) {
            System.out.print(sortedMap.get(iterator.next()));
            if (iterator.hasNext()) {
                System.out.print(" ");
            }
        }
        System.out.println();
    }

二 上述方案的优化版本(C++)(来自XTTT)

优化过后
        上面的方案太智障了,之后被学弟xttt吐槽了,还顺便帮我优化了下.
        优化思路大致就是只使用一个优先队列,代码简单,实现容易.
我之前是为每个数字开了一个优先队列,而XTTT直接使用一个队列,然后往队列里存储[数字,下标]的数据.然后一个个出栈做合并操作,合并完入栈,无法合并了(开设一个数组记录数字数量)就存入结果集


#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1

const int maxn=150010;
const long long INF=(1ll<<60)-1;
typedef long long ll;
typedef pair<ll,int> pli;
map<ll,int> cnt;
//用来存储数字的数量
map<int,ll> ans;

priority_queue<pli,vector<pli>,greater<pli> >Q;
int main(){
    //freopen("D://input.txt","r",stdin);

    int n;
    scanf("%d",&n);
    //往队列内存入 {数字,下标} 的数据
    for(int i=1;i<=n;i++){
        ll a;scanf("%I64d",&a);
        Q.push(make_pair(a,i));
        cnt[a]++;
    }
    //遍历队列
    while(!Q.empty()){
        pli it=Q.top();Q.pop();
        ll small=it.first;
        int p=it.second;
        //如果当前数字剩余数量大于1
        if(cnt[small]>1){
            //与下一个当前数字进行合并后入队列
            cnt[small]-=2;
            cnt[small*2]++;
            pli it2=Q.top();Q.pop();
            it2.first*=2;
            Q.push(it2);
        }
        else{
            //若只剩一个了,则存入结果集
            cnt[small]--;
            ans[p]=small;
        }
    }

    printf("%d\n",ans.size());
    for(map<int,ll>::iterator it=ans.begin();it!=ans.end();it++){
        printf("%I64d ",it->second);
    }
    return 0;
}

三 因式分解方式实现(c++)(来自dadi)

dadi因式分解
        这是我当初的队友dadi(大地)的实现方案,以前打比赛的时候就一直觉得他数学思维能力很强,所以我十分好奇他会以什么样的思路来解这道题,果不其然,他一上来就看出了这题数据的本质.
        他将所有数字分解成 k*(2^n)的形式,可以较快得出最后留下的数字是啥,但是最终下标的记录方式与我们有点类似,具体实现我也没看,直接上代码吧.


struct P{
    int ss,lv,tos;
}p[150005];
struct Q{
    int tos;
    long long lv;
};
Q s[150005];
bool comp (P a, P b)
{
    if (a.ss != b.ss)
        return a.ss < b.ss;
    return a.tos < b.tos;
}
int main()
{
    int n;
    scanf("%d",&n);

    int a;
    for (int i = 0; i < n; i++){
        scanf("%d",&a);
        int j = 1;
        while(a % 2 == 0){
            j *= 2;
            a >>= 1;
        }
        p[i].ss = a;
        p[i].lv = j;
        p[i].tos = i;
    }
    sort(p, p + n, comp);
    long long aa[150005];
    memset(aa, 0, sizeof(aa));
    int e = 0;
    for (int i = 0; i < n;)
    {
        long long sum = 0;
        int k = 0;
        int j;
        for (j = i ; j < n; j++ )
        {
            if (p[j].ss != p[i].ss)
                break;
            s[k].lv = p[j].lv;
            s[k++].tos = p[j].tos;
            sum += p[j].lv;
        }
        long long ans = p[i].ss;
        i = j;
        int b[50];
        memset(b, 0, sizeof(b));
        long long temp = sum;
        int bb;
        for (j = 0 ; temp; j++)
        {
            if (temp % 2){
                b[j] = 1;
            }
            temp /= 2;
        }
        bb = j;
        temp = 1;
        for (j = 0; j < bb; j++){
            int c = 0;
            bool d = true;
            for (int l = k - 1; l >= 0; l--){
                if (s[l].lv == temp)
                    c++;
                else
                    continue;
                if (d && b[j]){
                    //printf("&%d %d %lld %lld\n", j, s[l].tos, temp, ans);
                    aa[s[l].tos] = ans * temp;
                    e++;
                    d = false;
                }
                if ((c + b[j]) % 2 == 1)
                    s[l].lv *= 2;
                else
                    s[l].lv = 0;
            }
            temp *= 2;
        }

    }
    printf("%d\n", e);
    for (int i = 0, j = 0; i < n; i++){
        if (aa[i]){
            if (j)
                printf(" ");
            printf("%lld",aa[i]);
            j++;
        }
    }
    printf("\n");
}

四 一致认为的最优解(c++)(来自小笼包学弟)

        小笼包学弟是我快毕业的时候刚来我们ACM队的,也是我们学院史(实际上也没几年233)上第一个AK过入队比赛的(由我亲手送上奖励233)
        小笼包学弟摸上这题没多久就做完了,而且实现上也是最简单易懂的.我们一致认为这种方法算是标解,算是方案一,二的最终进化版了.
        实际上这道题的逻辑顺序就是从左到右执行,扫一次就能完成,前面的方法都有点想太多了.小笼包的做法是输入一个数就存入map,若已存在,则将这个数map中的值置为null,然后把(key:这个数*2,value:下标)存入这个map中(每次存入都得判断是否已存在).
结果

typedef long long lint;

const int MAXN = 2E5;

map<lint,int> mp;
vector<lint> ans;
lint n,cnt;
bool can[MAXN];

int main() {
    while(scanf("%d",&n)!=EOF){
        mp.clear(); ans.clear(); cnt = 0; memset(can,true,sizeof(can));
        for(int i = 1; i <= n; ++i){
            lint num; scanf("%I64d",&num); ans.push_back(num);
            while(mp[num] != NULL){
                ans[i - 1] *= 2; can[mp[num] - 1] = false; mp[num] = NULL; num *= 2; ++cnt;
            }
            mp[num] = i;
        }
        printf("%d\n",n - cnt);
        for(int i = 0; i < ans.size(); ++i){
            if(can[i]){
                printf("%I64d ",ans[i]);
            }
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/jiangxiewei/article/details/79948270