前言
这场有能力 AK 的,可惜 F 题debug 的时候浪费了太多时间。
Standings:721
本次比赛结束后上蓝名,记录一下。(虽然是本就应该的hhh)
题目链接:Dashboard - Codeforces Round 957 (Div. 3) - Codeforces
A. Only Pluses
显然,每次操作都让最小的那个数加一就能得到最大乘积。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int T,n,a[5];
int main()
{
scanf("%d",&T);
while (T --)
{
for (int i = 1;i <= 3;++ i) scanf("%d",&a[i]);
for (int i = 1;i <= 5;++ i)
{
sort(a + 1,a + 4);
++ a[1];
}
printf("%d\n",a[1] * a[2] * a[3]);
}
return 0;
}
B. Angry Monk
贪心,类似于合并果子,排序后每次合并最小的即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 200005
int T,n,k,a[N];
long long ans;
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d%d",&n,&k),ans = 0ll;
for (int i = 1;i <= k;++ i) scanf("%d",&a[i]);
sort(a + 1,a + k + 1);
for (int i = 1;i < k;++ i) ans += 1ll * (a[i] + a[i] - 1);
printf("%lld\n",ans);
}
return 0;
}
C. Gorilla and Permutation
构造题,分情况讨论即可。
#include<cstdio>
#include<cstring>
using namespace std;
#define N 200005
int T,n,m,k,cnt,a[N];
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d%d%d",&n,&m,&k),cnt = 0;
if(k > m)
{
for (int i = n;i >= m + 1;-- i) a[++ cnt] = i;
for (int i = 1;i <= m;++ i) a[++ cnt] = i;
}
else if(k == m)
{
for (int i = n;i >= m;-- i) a[++ cnt] = i;
for (int i = 1;i <= m - 1;++ i) a[++ cnt] = i;
}
else
{
for (int i = n;i >= k;-- i) a[++ cnt] = i;
for (int i = 1;i <= k - 1;++ i) a[++ cnt] = i;
}
for (int i = 1;i <= n;++ i) printf("%d ",a[i]);
printf("\n");
}
return 0;
}
D. Test of Love
简单DP。
#include<cstdio>
#include<cstring>
using namespace std;
#define N 200005
int T,n,m,k,a[N],f[N];
int min(int x,int y) { return x < y ? x : y ; }
int max(int x,int y) { return x > y ? x : y ; }
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d%d%d",&n,&m,&k),f[0] = a[0] = a[n + 1] = 0,f[n + 1] = 0x3f3f3f3f;
for (int i = 1;i <= n;++ i)
{
f[i] = 0x3f3f3f3f;
char s;
scanf(" %c",&s);
if(s == 'L') a[i] = 0;
if(s == 'W') a[i] = 1;
if(s == 'C') a[i] = 2;
}
for (int i = 1;i <= n + 1;++ i)
{
if(a[i] == 2) continue;
if(a[i - 1] == 1) f[i] = min(f[i],f[i - 1]);
for (int j = max(i - m,0);j <= i - 1;++ j)
if(!a[j])
f[i] = min(f[i],f[j]);
f[i] += (a[i] == 1);
}
if(f[n + 1] <= k) printf("YES\n");
else printf("NO\n");
}
return 0;
}
E. Novice's Mistake
思维题。
根据题目范围,n * a 不会超过1000000,于是 n * a - b 不会超过 999999,那么 s 在删去 b 位之后一定小于等于 6 位数。记 len 表示一个 n 的长度,则:a * len - b <= 6
直接枚举 a 和 b 时间复杂度太大,我们需要换个枚举方式。
记 k 表示 s 删除了 b 位之后的长度,满足 a * len - b = k <= 6,则 b = a * len - k 。
对于每个 n ,我们只需枚举 a 和 k 就可以得到 b,再判断是否合法即可。
#include<cstdio>
#include<cstring>
using namespace std;
#define N 10005
int T,n,len,s[5],t[5],cnt,ansa[N],ansb[N];
void swap()
{
for (int i = 1;i <= len;++ i) t[i] = s[len - i + 1];
for (int i = 1;i <= len;++ i) s[i] = t[i];
return;
}
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d",&n),len = cnt = 0;
int tmp = n;
while (tmp) s[++ len] = tmp % 10,tmp /= 10;
swap();
for (int a = 1;a <= 10000;++ a)
{
if(a * len > 10006) break;
for (int k = 1;k <= 6;++ k)
{
int b = a * len - k;
if(b < 1) break;
if(b > 10000) continue;
tmp = 0;
for (int i = 1;i <= k;++ i)
{
int now = i % len;
if(!now) now = len;
tmp = tmp * 10 + s[now];
}
if(tmp == a * n - b) ansa[++ cnt] = a,ansb[cnt] = b;
}
}
printf("%d\n",cnt);
for (int i = 1;i <= cnt;++ i) printf("%d %d\n",ansa[i],ansb[i]);
}
return 0;
}
F. Valuable Cards
贪心。
从左到右进行分组,贪心可知一定要尽可能让当前组里面数字的数目更多,于是在加入每个数的时候标记组里面的数可以产生的所有 x 的因数,要是已经能产生 x 了,那当前这个数就应该放在下一个组的第一个位置。
比赛的时候程序出 bug 了,注释掉后面的代码,前面输出的值竟然不一样,固然是因为某些地方写挂了,但这让调试十分困难,浪费了大量时间导致没有开 G 题。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100005
int T,n,x,a[N],yin[N],vis[N],cnt,tot;
int main()
{
scanf("%d",&T);
int tt = 0;
while (T --)
{
++ tt;
scanf("%d%d",&n,&x),cnt = tot = 0;
for (int i = 1;i <= n;++ i) scanf("%d",&a[i]);
for (int i = 2;i * i <= x;++ i)
if(!(x % i))
{
yin[++ cnt] = i;
vis[i] = vis[x / i] = 0;
if(i * i != x) yin[++ cnt] = x / i;
}
sort(yin + 1,yin + cnt + 1);
++ tot;
if(!(x % a[1])) vis[a[1]] = tot;
for (int i = 2;i <= n;++ i)
{
if(x % a[i] || a[i] == 1) continue;
if(vis[x / a[i]] == tot)
{
++ tot;
vis[a[i]] = tot;
continue;
}
for (int j = cnt; j ;-- j)
if(vis[yin[j]] == tot && (long long)yin[j] * a[i] < (long long)x)
vis[yin[j] * a[i]] = tot;
vis[a[i]] = tot;
}
printf("%d\n",tot);
}
return 0;
}
G. Ultra-Meow
数学计数题,由于太久没做出来这种题,在没看题解的情况下做出来还是开心的。
我们考虑枚举集合内的元素个数 k ,计算每个 k 的总贡献。首先不难发现,集合大小为 k 的某个集合,它单个的贡献范围是 [k+1,2k+1] 。因此对于每个 k ,我们枚举可能的贡献 x ,算出贡献为 x 的集合数目,再计算总贡献。
当贡献为 x 时:
1. 因为 x 是集合外的第 k + 1 个数字,因此在 x 之前(小于 x 的)一定有 x - (k+1) 个数字存在于集合内,这部分的方案数为 ,和 n 取 min 是因为有可能 n 比 x - 1 小,这样可选择的数字就只有 n 个而不是 x - 1 个。
2. 满足了 “1.” 中的条件后,若集合在 x 之后(大于 x 的)还有数字,那就随便选了,这部分的方案数是
根据乘法原理,总方案数(集合数目)就是 。
很奇怪的是好多人用 c++ 11 / 14 / 17 都 TLE 了,c++ 20 就能过,也许是 c++ 20 快点儿?
#include<cstdio>
#include<cstring>
using namespace std;
#define M 1000000007
#define N 5005
int T,n;
long long c[N][N],ans;
int min(int x,int y) { return x < y ? x : y ; }
long long ksm(long long x,long long p)
{
long long tmp = 1ll;
while (p)
{
if(p & 1) tmp = tmp * x % M;
x = x * x % M;
p >>= 1;
}
return tmp;
}
void pre()
{
for (int i = 1;i <= 5000;++ i)
{
c[i][0] = c[i][i] = 1ll;
for (int j = 1;i - j + 1 > j;++ j)
c[i][j] = c[i][i - j] = c[i][j - 1] * 1ll * (i - j + 1) % M * ksm(1ll * j,M - 2ll) % M;
}
return;
}
int main()
{
pre();
scanf("%d",&T);
while (T --)
{
scanf("%d",&n),ans = 1ll;
for (int k = 1;k <= n;++ k)
for (int x = k + 1;x <= 2 * k + 1;++ x)
{
int up1 = x - (k + 1);
int low1 = min(x - 1,n);
int up2 = 2 * k - x + 1;
int low2 = n - x;
int c1 = c[low1][up1];
int c2 = (!up2) ? 1 : ((low2 < up2) ? 0 : c[low2][up2]);
ans = (ans + 1ll * x * c1 % M * c2 % M) % M;
}
printf("%lld\n",ans);
}
return 0;
}
总结
感觉这场比赛打的还是挺顺的,除了最后调试那里出了点 bug ,后来发现可能是多组数据初始化的时候没清干净导致的神奇问题(虽然理论上是没有问题的,但是既然出 bug 了那以后就要注意)。感觉自己的思维在慢慢提升,争取能尽快在比赛时突破 6 题,早日 AK 一次!