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", 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], " \n"[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\n divisor\n", 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\n", ans % MOD);
            continue;
        }
        if(y % x == 0) {
            ll ans = Solve(y / x);
            printf("%lld\n", 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\n", len_xGy);
        //printf("len xLy=%lld\n", len_xLy);
        if(len_xGy < len_xLy) {
            ll xg = Solve(x / G);
            ll yg = Solve(y / G);
            ll ans = xg * yg % MOD;
            printf("%lld\n", 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\n", ans % MOD);
            continue;
        } else {
            ll xL = Solve(L / x);
            ll yL = Solve(L / y);
            ll ans = xL * yL % MOD;
            printf("%lld\n", ans);
            continue;
        }
    }
    return;
}
posted @ 2020-04-11 00:51  KisekiPurin2019  阅读(245)  评论(0编辑  收藏  举报