• Educational Codeforces Round 85 (Rated for Div. 2)


    好像终于打上2100分了,从去年11月初就想这个事想到现在了,接下来要兑现诺言,无论发生什么都要去参加下半年的区域赛。

    题目链接:https://codeforces.com/contest/1334

    A - Level Statistics

    特别恶心的题意,实际上却随便判断一下就行了。还好交之前想了一下有没有特殊情况,不然真就FST了就哭出声。

    B - Middle Class

    比A还好做,排个序,依次选最大的k个进行平均,验证这k个是否能同时到达中产。

    C - Circle of Monsters

    比B还好做,全场最简单题。随便取两个最小值搞定。

    首先贪心把每个怪兽打到刚刚好可以被上一个怪兽炸死,然后选生命值最小的一个怪兽补刀。仔细想了一下上一个怪兽伤害为0的情况,发现没有特例。

    *D - Minimum Euler Cycle

    做了挺久的,用了一个很不优美的实现(杜老师的实现特别优美)。

    题意:最小欧拉回路,给一个n个点的有向完全图(没有自环,任意不同的两点之间有且仅有两条互相反向的有向边),输出其最小的欧拉回路的[l,r]区间内的节点顺序。

    画了几个小的样例观察:

    n=2:

    1 2 1
    

    n=3:

    1 2 1 3 2 3 1
    

    n=4:

    1 2 1 3 1 4 2 3 2 4 3 4 1
    

    原本以为是每次从上一个n迭代构造,但是看见n这么大感觉不对,然后画了n=5。

    n=5:

    1 2 1 3 1 4 1 5 2 3 2 4 2 5 3 4 3 5 4 5 1
    

    这个就很明显了,奇数位置的依次是4个1,3个2,2个3,1个4,1个1,偶数位置的依次是[2,5],[3,5],[4,5],[5,5],不过当时没想到这么方便的构造,当时想的是,按同样的奇数位置分段,然后先二分找到最近的一段,再逐个平移到l,一个一个输出到r。

    ll n;
    ll l, r;
    ll sum, cur;
     
    ll Sum(ll x) {
        ll a1 = (n - x) * 2ll;
        ll d = 2ll;
        ll ax = a1 + (x - 1ll) * d;
        ll S = (a1 + ax) * x / 2ll;
        return S;
    }
     
    bool check(ll x) {
        return Sum(x) < l;
    }
     
    void bs() {
        ll L = 1ll, R = n;
        while(1) {
            ll M = (L + R) >> 1;
            if(L == M) {
                if(check(R)) {
                    cur = R + 1ll;
                    sum = Sum(R);
                    assert(cur <= n);
                    return;
                } else {
                    cur = 1ll;
                    sum = 0ll;
                    return;
                }
            }
            if(check(M))
                L = M;
            else
                R = M - 1ll;
        }
    }
     
    ll ans[300005], atop;
     
    void TestCase() {
        scanf("%lld", &n);
        scanf("%lld%lld", &l, &r);
        if(l == r && r == 1ll * n * (n - 1) + 1) {
            puts("1");
            return;
        }
        //printf("n=%lld [%lld,%lld]
    ", n, l, r);
        sum = 0;
        cur = -1;
        bs();
        while(sum + (n - cur) * 2ll < l) {
            sum += (n - cur) * 2ll;
            ++cur;
        }
        ll tmp = cur + 1;
        int firstiscur = 1, firstistmp = 0;
        while(sum < l - 1) {
            ++sum;
            if(firstiscur) {
                firstiscur = 0;
                firstistmp = 1;
            } else {
                firstiscur = 1;
                firstistmp = 0;
                tmp += 1;
                if(tmp == n + 1) {
                    ++cur;
                    tmp = cur + 1;
                }
            }
        }
        atop = 0;
        while(sum < r) {
            ++sum;
            if(firstiscur) {
                firstiscur = 0;
                firstistmp = 1;
                if(cur == n)
                    cur = 1;
                ans[++atop] = cur;
            } else {
                firstiscur = 1;
                firstistmp = 0;
                ans[++atop] = tmp;
                tmp += 1;
                if(tmp == n + 1) {
                    ++cur;
                    tmp = cur + 1;
                }
            }
        }
        for(int i = 1; i <= atop; ++i)
            printf("%lld%c", ans[i], " 
    "[i == atop]);
        return;
    }
    

    但是为什么是这样二分,当时比赛的时候没有想清楚,测了好多种情况都比较正常就提交了,看看能不能证明一下这样构造有没有漏洞。

    *E - Divisor Paths

    题意:给一个很大的数D,把他的所有因子视作节点,点权就是因子的大小,两个节点之间,若点权满足整除关系,则连接一条无向边,边权为“大数的因子集合有而小数的因子集合没有的数的个数”。

    每次询问一对(u,v),找出他们之间的“最短路有多少种”。

    题解:传说中的“最短路有多少种”出现了,容易看到实际上两个数若满足整除关系,最短路就是依次除掉多出来的质因子的过程,那么最短路的种类肯定是等于多出来质因子的可重排列,那么只需要算出多出来的质因子是什么样的分布就可以了。这里要是暴力分解质因子就会复杂度爆炸,但是因为这些数都是D的因子,所以都是由D的质因子种类组成,一开始预处理出D的质因子种类,然后分解质因子的时候就只需要找这些质因子就可以了。那么不整除的情况很明显就通过GCD归约成整除的情况,当时还以为要考虑LCM的长度,但是实际上LCM的长度一定是会更长的。

    证明如下:记x的因子个数为D(x),由常识可知D(x)是积性函数,

    所以要证明:
    D(LCM)-D(x)+D(LCM)-D(y)>=D(x)-D(GCD)+D(y)-D(GCD)

    即证明:
    D(xy/GCD)+D(GCD)>=D(x)+D(y)

    即证明:
    D(xy/GCD)/D(GCD)+D(GCD)/D(GCD)>=D(x)/D(GCD)+D(y)/D(GCD)

    即证明:
    D(x/GCD)*D(y/GCD)+D(1)>=D(x/GCD)+D(y/GCD)

    可以看作二元函数:
    xy-x-y+1>=0

    对x和y分别求偏导,得出最小值在x=1或=1处取得,所以证明正确。

    从上式也可以看出,只有整除的时候才会取等号。

    const ll MOD = 998244353;
     
    ll D;
    int q;
     
    ll P[1005];
    int Ptop;
     
    ll cntDivisor(ll x) {
        //printf("x=%lld", x);
        ll ans = 1;
        for(int i = 1; i <= Ptop; ++i) {
            ll tmp = 0;
            while(x % P[i] == 0) {
                ++tmp;
                x /= P[i];
            }
            ans = ans * (tmp + 1);
        }
        //printf(" has %lld
     divisor
    ", ans);
        return ans;
    }
     
    ll qpow(ll x, ll n) {
        if(x >= MOD)
            x %= MOD;
        ll res = 1;
        while(n) {
            if(n & 1) {
                res = res * x;
                if(res >= MOD)
                    res %= MOD;
            }
            x = x * x % MOD;
            if(x >= MOD)
                x %= MOD;
            n >>= 1;
        }
        return res % MOD;
    }
     
    ll Solve(ll x) {
        ll sumtmp = 0;
        ll sumB = 1;
        for(int i = 1; i <= Ptop; ++i) {
            ll tmp = 0;
            ll sumb = 1;
            while(x % P[i] == 0) {
                ++tmp;
                sumb = sumb * tmp;
                if(sumb >= MOD)
                    sumb %= MOD;
                x /= P[i];
            }
            sumB = sumB * sumb;
            if(sumB >= MOD)
                sumB %= MOD;
            sumtmp += tmp;
        }
        ll sumA = 1;
        while(sumtmp) {
            sumA = sumA * sumtmp;
            if(sumA >= MOD)
                sumA %= MOD;
            --sumtmp;
        }
        return sumA * qpow(sumB, MOD - 2) % MOD;
    }
     
    void TestCase() {
        scanf("%lld%d", &D, &q);
        Ptop = 0;
        ll CD = D;
        for(ll x = 2; x * x <= CD; ++x) {
            if(CD % x == 0) {
                P[++Ptop] = x;
                while(CD % x == 0)
                    CD /= x;
            }
        }
        if(CD != 1)
            P[++Ptop] = CD;
        while(q--) {
            ll x, y;
            scanf("%lld%lld", &x, &y);
            if(x % y == 0) {
                ll ans = Solve(x / y);
                printf("%lld
    ", ans % MOD);
                continue;
            }
            if(y % x == 0) {
                ll ans = Solve(y / x);
                printf("%lld
    ", ans % MOD);
                continue;
            }
            ll G = __gcd(x, y);
            ll L = x / G * y;
            assert(L <= D);
            ll len_xGy = cntDivisor(x) - 2ll * cntDivisor(G) + cntDivisor(y);
            ll len_xLy = 2ll * cntDivisor(L) - cntDivisor(x) - cntDivisor(y);
            //printf("len xGy=%lld
    ", len_xGy);
            //printf("len xLy=%lld
    ", len_xLy);
            if(len_xGy < len_xLy) {
                ll xg = Solve(x / G);
                ll yg = Solve(y / G);
                ll ans = xg * yg % MOD;
                printf("%lld
    ", ans % MOD);
                continue;
            } else if(len_xGy == len_xLy) {
                ll xg = Solve(x / G);
                ll yg = Solve(y / G);
     
                ll xL = Solve(L / x);
                ll yL = Solve(L / y);
                ll ans = xg * yg % MOD + xL * yL % MOD;
                printf("%lld
    ", ans % MOD);
                continue;
            } else {
                ll xL = Solve(L / x);
                ll yL = Solve(L / y);
                ll ans = xL * yL % MOD;
                printf("%lld
    ", ans);
                continue;
            }
        }
        return;
    }
    
  • 相关阅读:
    VS2015生成64位dll文件
    gdb简单调试~core文件
    Ubuntu ENet 的下载和编译
    Android LIstView初次创建getview方法执行多次问题
    android ipc通信机制之之三,进程通讯方式。
    Android Stduio统计项目的代码行数
    Android开发遇到的坑(1):Java中List的安全删除问题
    Android Studio修改项目的包名
    类似IOS的滑动返回上一级,SwipeBackLayout-android的滑动返回类库
    [转]理解RESTful架构
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12677383.html
Copyright © 2020-2023  润新知