zoukankan      html  css  js  c++  java
  • 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博客

  • 相关阅读:
    Docker 安装及使用
    明明白白学 同步、异步、阻塞与非阻塞
    ArrayList 并发操作 ConcurrentModificationException 异常
    shell 脚本防止ddos
    shell 脚本备份数据库
    shell 脚本猜数字
    shell 脚本检测主从状态
    tomcat 结合apache 动静分离
    shell 脚本检测网站存活
    zabbix 4.0 版本 yum安装
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/11818856.html
Copyright © 2011-2022 走看看