Codeforces Round #596 (Div. 2, based on Technocup 2020 Elimination Round 2)

A - Forgetting Things

题意:给 (a,b) 两个数字的开头数字(1~9),求使得等式 (a=b-1) 成立的一组 (a,b) ,无解输出-1。

题解:很显然只有 (a=b)(a=b-1) 的时候有解,再加上一个从 (9) 越到 (10) 的就可以了。被样例的 (199,200) 误导了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int a, b;
    while(~scanf("%d%d", &a, &b)) {
        if(a == b)
            printf("%d %d
", a * 10, b * 10 + 1);
        else if(a == b - 1)
            printf("%d %d
", a, b);
        else if(a == 9 && b == 1)
            printf("9 10
");
        else
            puts("-1");
    }
}

B1 - TV Subscriptions (Easy Version)

见下一题

B2 - TV Subscriptions (Hard Version)

题意:有n天,每天上映一段电视剧的第ai节,要连续看d天,求最少买多少张票。注意买了某一节的票之后就可以反复看这一节。

题解:看数据量,是不是可以二分票的张数,然后尺取暴力验证呢?不过真的t会卡memset,而k的上限又没给,假如想用memset的话要先进行一次离散化。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int n, k, d;
int a[200005];
int cnt[1000005];

bool check(int m) {
    int cur = 0;
    for(int i = 1; i <= d; ++i) {
        ++cnt[a[i]];
        if(cnt[a[i]] == 1)
            ++cur;
    }
    if(cur <= m) {
        for(int i = 1; i <= d; ++i)
            cnt[a[i]] = 0;
        return 1;
    }
    for(int i = d + 1; i <= n; ++i) {
        ++cnt[a[i]];
        if(cnt[a[i]] == 1)
            ++cur;
        --cnt[a[i - d]];
        if(cnt[a[i - d]] == 0) {
            --cur;
            if(cur <= m) {
                for(int j = 0; j < d; ++j)
                    cnt[a[i - j]] = 0;
                return 1;
            }
        }
    }
    for(int j = 0; j < d; ++j)
        cnt[a[n - j]] = 0;
    return 0;
}

int bs() {
    int l = 1, r = d, m;
    while(1) {
        m = (l + r) >> 1;
        if(l == m) {
            if(check(l))
                return l;
            return r;
        }
        if(check(m))
            r = m;
        else
            l = m + 1;
    }
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d%d", &n, &k, &d);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        printf("%d
", bs());
    }
}

补题题解:其实并不需要二分,扫的时候就知道最小值是谁了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int n, k, d;
int a[200005];
int cnt[1000005];

int solve() {
    int cur = 0;
    for(int i = 1; i <= d; ++i) {
        ++cnt[a[i]];
        if(cnt[a[i]] == 1)
            ++cur;
    }
    int mincur = cur;
    for(int i = d + 1; i <= n; ++i) {
        ++cnt[a[i]];
        if(cnt[a[i]] == 1)
            ++cur;
        --cnt[a[i - d]];
        if(cnt[a[i - d]] == 0) {
            --cur;
            if(cur < mincur)
                mincur = cur;
        }
    }
    for(int j = 0; j < d; ++j)
        cnt[a[n - j]] = 0;
    return mincur;
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d%d", &n, &k, &d);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        printf("%d
", solve());
    }
}

C - p-binary

题意:给一个整数n,求它最少用多少个p-binary数组成,无解输出-1,p-binary数是指形如 (2^x+p) 的数,其中x非负。

题解:看了下样例,感觉就是不断的尝试减去p(记总共减去了cnt个p),然后判断新的n能不能被cnt个二进制位所表示。不过样例提示我们可以用多个相同的二进制位合成一个高位,所以只需要新的n的二进制位数量不超过cnt就可以了。不过这样做卡了一个数据就是n=101,p=50,这个是输出-1,我这样写输出2,原因是1并不能被2个二进制位表示,因为不能取x=-1,所以得到另一个启发就是k个二进制位能表示的下限就是k(k个1相加),上限是无穷,但是能表示的数必须不超过cnt个1。

为什么比cnt少的二进制位都可以构造呢?因为可以用两个小的组成一个大的来使得cnt变相减少1,而这种方法使用的下限就是cnt个1。

顺带提一下__builtin_popcount()这个函数,其他的不好用但是这个(以及__builtin_ctz()返回后导零的个数)还是不错的。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int n, p;
    while(~scanf("%d%d", &n, &p)) {
        int cnt = 0;
        while(cnt < 40) {
            n -= p;
            if(n <= 0) {
                cnt = -1;
                break;
            }
            ++cnt;
            if(__builtin_popcount(n) <= cnt && n >= cnt)
                break;
        }
        if(cnt == 40)
            cnt = -1;
        printf("%d
", cnt);
    }
}

D - Power Products

题意:给 (n)(2e5)个1e5内的数和 (k) (100),求有多少个无序数对 ((i,j)) 满足存在一个 (x) 使得 (a_ia_j=x^k) 成立。

题解:

很显然就是每种质因数都要是k的倍数,那么每个数要找的那个数是在模意义下面唯一的,一个暴力的做法是分解1e5内的质数,然后求出不超过1e5的这个质数的最高幂,很明显这个最高幂很快就收敛到只剩下1了,那么可以用一个next指针为[0,16](也就是log(2,1e5))的字典树来存储。这样到后面一般只有两条路可以走。问题在于由于质数过多深度太大。

一种简单的改进就是特判掉k=2的情况,然后质数的范围就真的缩小到sqrt(1e5),超过这个范围的质数最多只有一个,且出现以后不可能会和另一个数配出k>=3时的k的倍数。这样是大概只有65层的一个字典树。

那怎么特判掉k=2的情况呢?应该是存在同一个大质因数的放在一堆,然后堆内匹配。不存在任何大质因数的也是一堆,也是堆内匹配。

补题题解:

本质上是要找唯一配对的一个质因数幂次序列,使用字典树会导致单次修改要经历所有的质数。其实根据唯一分解定理,把这些质因数幂次序列转回去整数存储就可以了,不用搞这么复杂。直接把一个数映射到幂模k的另一个数,然后用map存下来就可以了。甚至不需要用map,他的补数也必须限制在1e5里面的,在找补数的时候注意溢出就可以(使用map的话,应该是可以连溢出都不用管,因为溢出之后大部分应该是找不到答案,应该没有概率说溢出之后绕回来到1e5内)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int MAXAI = 1e5;
int cnt[100005];

int p[105], ptop;
bool np[405];

ll qpow(ll x, int n) {
    ll res = 1;
    while(n) {
        if(x > MAXAI)
            return -1;
        if(n & 1) {
            res = res * x;
            if(res > MAXAI)
                return -1;
        }
        x = x * x;
        //不能在这里判断x溢出,因为有可能这个x不会叠到res上
        n >>= 1;
    }
    return res;
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    for(int i = 2, c = sqrt(MAXAI); i <= c; ++i) {
        if(np[i])
            continue;
        p[++ptop] = i;
        for(int j = i + i; j <= c; j += i)
            np[j] = 1;
    }

    int n, k;
    scanf("%d%d", &n, &k);
    ll ans = 0;
    for(int i = 1, ai; i <= n; ++i) {
        scanf("%d", &ai);
        ll bi = 1, ci = 1;
        for(int j = 1; j <= ptop; ++j) {
            if(ai % p[j] == 0) {
                int ki = 0;
                while(ai % p[j] == 0) {
                    ++ki;
                    ai /= p[j];
                }
                ki %= k;
                if(ki) {
                    //假如ki==0,那么k-ki==0,bi和ci都不会变化
                    ci *= qpow(p[j], ki);
                    if(bi != -1) {
                        ll tmp = qpow(p[j], k - ki);
                        if(tmp != -1) {
                            bi *= tmp;
                            if(bi > MAXAI)
                                bi = -1;
                        } else
                            bi = -1;
                    }
                }
            }
        }
        if(ai > 1) {
            ci *= ai;
            if(bi != -1) {
                ll tmp = qpow(ai, k - 1);
                if(tmp != -1) {
                    bi *= tmp;
                    if(bi > MAXAI)
                        bi = -1;
                } else
                    bi = -1;
            }
        }
        if(bi != -1)
            //当bi不溢出时,看看前面是不是已经统计了这个bi
            ans += cnt[bi];
        ++cnt[ci];
    }
    printf("%lld
", ans);
}

注:快速幂里面的溢出不是在x=x*x之后判断,因为有可能会有n>>=1之后n==0,也就是最后一步并不会让res叠上x。

标签:质因数分解

E - Rock Is Push

题意:一个n*m(2000*2000)的棋盘格,每次只能向右或向下走,求走到最右下的不同路径数.注意棋盘格上有石头,可以推动石头,也可以推动一串石头,但是不能把石头推出边界。

补题题解:很显然的一个dp,但是要怎么入手呢?先说一个显然的结论,就是当前行/列的石头只会影响你在这个行/列上前进的终点,在改变方向只会这些石头会再有没有用,所以说每次只需要考虑当前方向上的石头即可。

(dp[i][j][0]) 表示从左侧走到格子 ((i,j)) 的方案数。
(dp[i][j][1]) 表示从上侧走到格子 ((i,j)) 的方案数。

那么怎么转移呢?举个例子,比如现在求的是 (dp[i][j][1]) ,从上方进来的话,会有最后一次向右走的位置,记这个位置为 ((k,j)) ,也就是从左侧进入了 ((k,j)) ,然后一直往下到 ((i,j)) ,什么是合法的 ((k,j)) 呢?当然就是 ((k,j)) 下方的石头的数量不超过 ((i,j)) 下方的空格的数量的 ((k,j)) ,可以看出来随着 (k) 不断往上途径的石头会越来越多,这个转折点是确实存在的。一个细节在于这个 ((k,j)) 这行能不能通过向右走到 ((k,j)) 是无关紧要的,反正也就加在一起,事实上 ((k,j)) 应该是上边界某点或者正好在某块石头上(这块石头会被向右推走而不会被向下推,k再上升则会被向下推)

也就是 (dp[i][j][1]=sumlimits_{t=k}^{i-1}dp[t][j][0])
同理有 (dp[i][j][0]=sumlimits_{t=k}^{j-1}dp[i][t][1])

方程有了,边界怎么办? ((1,1)) 格子既可以视作从左侧来也可以视作从上侧来,只是 (n=m=1) 的时候要特判掉。

怎么找k呢?以向下转移为例,很显然每个格子下方的石头数是递增的,可以保存每一行每一列的值直接二分,鉴于数据只有2000所以多个11倍常数问题不大。另一种方法是直接从上方的格子转移。

参考资料:

Technocup 2020 — Elimination Round 2 + Codeforces Round 596: analysis - Codeforces
CodeForces 1225E Rock Is Push(dp + 前缀和优化) - alpc_qleonardo - CSDN博客

原文地址:https://www.cnblogs.com/KisekiPurin2019/p/11818856.html