cf891A Pride 题解

有史以来第一次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 
#include 
#include 
#include 
using namespace std;
typedef long long LL;
typedef map 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<else if(res == INF) cout<<"-1"<else cout<1<return 0;
}

你可能感兴趣的:(dp,数论)