cf891A Pride 题解

13 篇文章 0 订阅

有史以来第一次rating为正..手速之力是无穷的!(雾)
这题还是蛮有趣..想到了区间DP但是发现转移不动,还是naive..


来看看题意。一串数,每次只能将相邻两个元素中的一个变成1,问要求最少多少次GCD才能全为1。如果不能,就输出-1。
如:
[2, 2, 3, 4, 6]变换:
- [2, 1, 3, 4, 6]
- [2, 1, 3, 1, 6]
- [2, 1, 1, 1, 6]
- [1, 1, 1, 1, 6]
- [1, 1, 1, 1, 1]

需要5次。


暴搜复杂度显然会爆。
考虑如果数列中含有 1 ,那么显然1的两边是都可以变的,并且只需要 1 次。所以,长n的数列含有 k 1的话最少就是 nk
顺着这个思路,我们就是找出数列中变幻出第一个 1 的最少次数。
看一眼范围,O(n2),可以考虑二维DP,自然而然的我们设计这样一个状态: dp[L][R] 表示区间 [L,R] 届到 1 需要的最小次数。但是这样还是很不方便转移,甚至难以初始化。
因此我们换一种状态表示方法:dp[L][R]表示区间 [L,R] 的公共gcd。显然,该问题可解的充要条件是 dp[L,R]=1
而转移也很显然: dp[L][R]=gcd(dp[L][R1],s[R])
求出这个之后,我们的结果显然就是 RL+n1=RL+n1 ,因为每次递推过程都可以看做进行一次gcd,所以区间长度表示gcd次数。
这样这个题是可以过的。


在讨论区偶然看到,这道题可以优化到 O(nlogn) ,用数据结构维护。
看到之后也是深深被折服,解法确实漂亮。
讨论帖给出的地址:http://paste.ubuntu.com/25983311/
考虑一下我们维护的是什么,显然是届到 1 需要的次数。
因此我们可以用map来维护达到某一个因数最后的位置,并不断更新一段区间达到的因数。来举个例子吧,以样例2 2 3 4 6为例
首先输入2,然后向 m 集合中扔进去m[2]=1表示在位置 1 达到gcd=2
然后输入 2 m更新为 m[2]=2 表示位置 2 就可以达到gcd=2
然后输入 3 gcd(2,3)=1,则 m[1]=2 ,表示区间 [2,3] 可以达到 1 。这时,因数2已经被更新掉了,需要删掉,所以这个时候 m 中的元素只有m[2]=1 m[3]=3
此时,由于达到了因数 1 ,因此可以更新变量res表示最小达到 1 的区间长度,此时为1。
再输入4 gcd(2,4)=1 m[1]=2 gcd(3,4)=1 m[1]=3 res=min(res,43)=1 m m[1]=3, m[4]=4
以此类推即可,最后输出 n+res1
但是这样很不方便实现更新操作,因此我们可以另外开一个 e 集合,来存储每一次m更新后的结果,然后 m 的下一次更新从e中取出更新。

总感觉是个玄学复杂度..有dalao 帮忙分析一下吗(逃)


在下的AC 抄写 代码:

#include <iostream>
#include <cstdio>
#include <climits>
#include <map>
using namespace std;
typedef long long LL;
typedef map<LL, LL> mll;
typedef mll::iterator mlt;
#define X first
#define Y second
#define INF INT_MAX >> 2
LL n;
mll upd, tmp;
inline void read(LL &x) {
    x = 0; char c = getchar();
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
inline LL gcd(LL x, LL y) {
    return y == 0 ? x : gcd(y, x % y);
}
int main() {
    read(n);
    LL now, k, res = INF, cntone = 0;
    for(int i = 1; i <= n; ++i) {

        read(now);
        if(now == 1) ++cntone;
        upd.clear();
        upd[now] = i;
        for(mlt it = tmp.begin(); it != tmp.end(); ++it) {
            k = gcd(it->X, now);
            upd[k] = max(upd[k], it->Y);
        }
        tmp.clear();
        for(mlt it = upd.begin(); it != upd.end(); ++it) {
            tmp[it->X] = it->Y;
            if(it->X == 1) {
                res = min(res, i - it->Y);
            }
        }
    }
    if(cntone) cout<< n - cntone<<endl;
    else if(res == INF) cout<<"-1"<<endl;
    else cout<<res + n - 1<<endl;
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值