Educational Codeforces Round 86 (Rated for Div. 2)
https://codeforces.com/contest/1342
以后<=1900分的题都不想写题意题解了……除非现场做不出或者WA几个点……
毕竟感觉很难形容步骤,说得不清不楚。
A - Road To Zero
随便贪一下。
B - Binary Period
假如是同种字符,周期是1,直接输出原序列。
否则周期至少就是2,恰好可以构造这个2。
C - Yet Another Counting Problem
a和b太小了,直接暴力预处理出[1,ab]。
久了不写各种演。
int a, b, q;
int c[80005];
void TestCase() {
scanf("%d%d%d", &a, &b, &q);
int T = a * b;
for(int i = 1; i <= 2 * T; ++i) {
int c1 = i % a % b;
int c2 = i % b % a;
c[i] = c[i - 1] + (c1 != c2);
}
while(q--) {
ll l, r;
scanf("%lld%lld", &l, &r);
ll ans = (r - l) / T * c[T];
l %= T;
r %= T;
if(r < l)
r += T;
ans += c[r] - (l >= 1 ? c[l - 1] : 0);
printf("%lld ", ans);
}
puts("");
return;
}
D - Multiple Testcases
二分乱搞?但是写出二分之后,发现check的时候好像要用线段树?然后发现用了线段树之后就不需要二分了。
但是用线段树只是为了实现区间加减,然后在所有修改之后再求一次最值,这个直接打差分标记就可以了。
然后求出需要多少组case之后,有个很明显的贪心就是往剩余限制最大的case填充,但是“剩余限制”这个东西蛮复杂的。
实际上如果按从小到大的顺序填充的话,可以直接把一个元素变大。想到这一点之后,立刻就可以知道是每组case轮流填充。
用个vector统计一下,复杂度线性。
int n, k;
int a[200005];
int c[200005];
int cnta[200005];
int dif[200005];
int suffix[200005];
vector<int> vec[200005];
void TestCase() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
cnta[a[i]] += 1;
dif[1] += 1;
dif[a[i] + 1] -= 1;
}
for(int i = 1; i <= k; ++i)
scanf("%d", &c[i]);
int ans = 0, curd = 0;
for(int i = 1; i <= k; ++i) {
curd += dif[i];
suffix[i] = curd;
ans = max(ans, (suffix[i] + c[i] - 1) / c[i]);
}
printf("%d\n", ans);
int top = 0;
for(int i = 1; i <= k; ++i) {
while(cnta[i]--) {
++top;
vec[top].push_back(i);
if(top == ans)
top = 0;
}
}
for(int i = 1; i <= ans; ++i) {
printf("%d", (int)vec[i].size());
for(auto &v : vec[i])
printf(" %d", v);
printf("\n");
}
return;
}
甚至连vector都不需要,155ms,1600KB,人生巅峰。
int n, k;
int a[200005];
int cnt[200005];
void TestCase() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
cnt[a[i]] += 1;
}
int ans = 0, curd = n;
for(int i = 1, c; i <= k; ++i) {
scanf("%d", &c);
curd -= cnt[i - 1];
ans = max(ans, (curd + c - 1) / c);
}
for(int i = 1, top = 0; i <= k; ++i) {
while(cnt[i]--)
a[++top] = i;
}
printf("%d\n", ans);
for(int i = 1; i <= ans; ++i) {
int siz = 0;
for(int j = i; j <= n; j += ans)
++siz;
printf("%d", siz);
for(int j = i; j <= n; j += ans)
printf(" %d", a[j]);
printf("\n");
}
return;
}
*E - Placing Rooks
又看见组合题了。
题意:放 \(n*n\) 的棋盘上放 \(n\) 个车,攻击所有的格子,且有 \(k\) 对车互相攻击。求方法数。
题解:看这个 \(n\) 的规模看起来正解应该是迭代。 \(n\) 个车攻击所有的格子,那么是否必须满足“每行恰好有一个车”或者“每列恰好有一个车”其中之一呢?感觉确实是这样。一个车最多参与4对“互相攻击”(事实上根据后面的分析应该是2对),所以 \(k\) 太大肯定是 \(0\) 。先特判掉 \(k=0\) 的情况,此时是n的排列数。然后假如我们求出“每行恰好有一个车”的解法,只需要转置一下就是“每列恰好有一个车”的解法,且不重不漏。
那么假如上面的假设正确,当所有车排成一条直线时取得最大的合法的 \(k\) , \(k\) 减少1必须移动第一行或者最后一行的车, \(k\) 减少2是不是会有很多玩法呢(可以分别移动第一行和最后一行的车到不同的位置,或者从中间行取出一个车),毕竟他们可以在新的位置重新互相攻击。
那么若设第i列的车的数量为 \(c[i]\),总的互相攻击对数为:
\(\sum_{i=1}^{n} calc(c[i])\)
其中 \(calc(x)\) 函数应该是:
int calc(int x){
if(x<=1)
return 0;
return (2+(x-2)*2)/2;
}
那么就要先把把k拆成若干个 \(calc\) 的和,然后给每一列用组合数选出一些行。
那么设 \(dp[i][j][k]\) 为在前 \(i\) 行分别放一个车,他们分别占据了 \(j\) 列,已有的互相攻击的对数为 \(k\) 的方案数,那么有转移:
\(dp[i][j][k]=dp[i-1][j][k-2]+dp[i-1][j-1][k]\)
可惜 \(n\) 太大了,不然这就已经做完了。但是上式给了一个什么启发呢?确实不会。敦爷模式启动。
题解提示了下面的东西:
int calc(int x){
return x-1;
}
枚举 \(n\) 个车占有的列数为 \(j\),则有 \(n-j\) 对,所以要恰好有 \(k\) 对,则要占有恰好 \(n-k\) 列。
但是还是不知道怎么统计。
原来是要容斥。
问题转化为:“把 \(n\) 个不同球放在 \(n-k\) 个不同的盒子里,每个盒子至少要有 \(1\) 个球。”。
“第二类斯特林数”的定义是“把 \(n\) 个不同球放在 \(m\) 个相同的盒子里,每个盒子至少要有 \(1\) 个球。”,给这 \(m\) 个盒子乘上一个排列数就得到上面要求的东西。
ll qpow(ll x, int n) {
ll res = 1;
if(x >= MOD)
x %= MOD;
while(n) {
if(n & 1) {
res = res * x;
if(res >= MOD)
res %= MOD;
}
x = x * x;
if(x >= MOD)
x %= MOD;
n >>= 1;
}
return res;
}
ll S(int n, int m) {
ll sum = 0;
sum += qpow(m, n);
ll fac1 = 1, fac2 = 1;
for(int i = 1; i <= m; ++i) {
fac1 *= (m + 1 - i);
if(fac1 >= MOD)
fac1 %= MOD;
fac2 *= i;
if(fac2 >= MOD)
fac2 %= MOD;
ll tmp = (i & 1) ? (MOD - 1) : (1);
tmp *= fac1;
if(tmp >= MOD)
tmp %= MOD;
tmp *= qpow(fac2, MOD - 2);
if(tmp >= MOD)
tmp %= MOD;
tmp *= qpow(m - i, n);
if(tmp >= MOD)
tmp %= MOD;
sum += tmp;
if(sum >= MOD)
sum -= MOD;
}
sum *= qpow(fac1, MOD - 2);
if(sum >= MOD)
sum %= MOD;
return sum;
}
ll A(ll n, ll m) {
ll fac1 = 1;
for(int i = 1; i <= n; ++i) {
fac1 *= i;
if(fac1 >= MOD)
fac1 %= MOD;
}
ll fac2 = 1;
for(int i = 1; i <= n - m; ++i) {
fac2 *= i;
if(fac2 >= MOD)
fac2 %= MOD;
}
return (fac1 * qpow(fac2, MOD - 2)) % MOD;
}
void TestCase() {
int n;
ll k;
scanf("%d%lld", &n, &k);
if(k > n) {
puts("0");
return;
}
if(k == 0) {
ll fac = 1;
for(int i = 1; i <= n; ++i) {
fac *= i;
if(fac >= MOD)
fac %= MOD;
}
printf("%lld\n", fac);
return;
}
int m = n - k;
ll fac = 1;
for(int i = 1; i <= m; ++i) {
fac *= i;
if(fac >= MOD)
fac %= MOD;
}
printf("%lld\n", 2ll * A(n, m) * S(n, m) % MOD);
}