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;
}