A - The Number of Even Pairs
题意
给你两个数$n, m$代表有$n$个偶数,$m$个奇数。让你输出$n$个偶数$m$个奇数从中任选两个数(没有顺序)相加结果为偶数的个数。
题解
相加为偶数只有偶加偶和奇加奇两种情况,其实就是在$n$个数中取两个($Cinom{2}{n}$),在$m$个数中取两个($Cinom{2}{m}$)。
时间复杂度$O(1)$
1 #include <iostream> 2 using namespace std; 3 int main() { 4 long long n, m; 5 cin >> n >> m; 6 cout << n * (n - 1) / 2 + m * (m - 1) / 2; 7 return 0; 8 }
B - String Palindrome
题意
定义一个字符串 $S$ 是强回文串当且仅当 $S$,$S_{1...frac{(|s|+1)}{2}}$ 和 $S_{|s|-frac{(|s|+3)}{2}+1...|s|}$都是回文的。判断一个回文串是不是强回文串。$3 leq |s| leq 99$且$|s|$是奇数。
题解
直接取出每一部分的字符串,判断到中心距离相等的位置的字符是否相等即可。
时间复杂度$O(|s|)$。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 char s[100], tmp[100]; 6 int top; 7 int n, nn; 8 int main() { 9 scanf("%s", s + 1); 10 n = strlen(s + 1); 11 nn = n; 12 for (int i = 1; i <= nn; i++) { 13 if (s[i] != s[nn - i + 1]) { 14 puts("No"); 15 return 0; 16 } 17 } 18 nn = (n - 1) / 2; 19 for (int i = 1; i <= nn; i++) { 20 if (s[i] != s[nn - i + 1]) { 21 puts("No"); 22 return 0; 23 } 24 } 25 nn = (n + 3) / 2; 26 for (int i = nn; i <= n; i++) { 27 tmp[++top] = s[i]; 28 } 29 for (int i = 1; i <= top; i++) { 30 if (tmp[i] != tmp[top - i + 1]) { 31 puts("No"); 32 return 0; 33 } 34 } 35 puts("Yes"); 36 return 0; 37 }
C - Maximum Volume
题意
给定一个整数x。求所有各棱长为实数且和为x的长方体中最大的体积是多少。
题解
小学奥数题...
和一定差小积大。
其实就是一个均值不等式。
设棱长为$a,b,c$,则 $a imes b imes c leq frac{(a+b+c)^3}{27}=frac{x^3}{27}$,当$a=b=c$时成立。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 double l; 5 int main() { 6 scanf("%lf", &l); 7 l = l / 3; 8 printf("%.12lf", l * l * l); 9 return 0; 10 }
D - Banned K
题意
我们有n个数,每个数都在[1,n]中,如果去掉第k个数,问剩下的数有多少对相同的数(不计顺序),$1 leq n leq 2 imes 10^5$。
题解
我们可以把 $n$ 个数中有多少相同的数对,因为每个数都很小所以这点我们可以用堆来做。
设 $cnt[i]$ 代表 $i$ 这个数出现的次数所以答案等于 $sum_{i=1}^{n}frac{cnt[i] imes (cnt[i]-1)}{2}$。
现在我们来看删掉一个值为 $x$ 的数。
$cnt[x]$ 的值减小了一,它的贡献变成了 $frac{(cnt[x]-1) imes (cnt[x]-2)}{2}$,相比之前减少了 $cnt[x]-1$。
设不删时的答案是 $tot$,则删掉一个权值为 $x$ 的数的答案为 $tot-(cnt[x]-1)$。
单词询问复杂度O(1)。
1 //这里sum就是cnt 2 #include <iostream> 3 #include <cstdio> 4 using namespace std; 5 const int N = 2e5 + 10; 6 int n, a[N]; 7 long long sum[N], tot; 8 int main() { 9 scanf("%d", &n); 10 for (int i = 1; i <= n; i++) scanf("%d", &a[i]), sum[a[i]]++; 11 for (int i = 1; i <= n; i++) { 12 tot += sum[i] * (sum[i] - 1) / 2; 13 } 14 for (int i = 1; i <= n; i++) { 15 printf("%lld ", tot - (sum[a[i]] - 1)); 16 } 17 return 0; 18 }
E - Dividing Chocolate
我太弱了,我比赛时这题竟然都没调出来。
题意
有一个$n imes m$的矩阵,每个位置要不是要不不是零,用尽量少的次数把这个矩阵切成几块(只能把整行或整列与下一行或列切开,具体可看下面的例子)使得每一块中1的个数少于某个常数。
$1 leq n leq 10, 1 leq m leq 1000$
题解
我们发现n非常的小,于是我们可以枚举行之间且的情况,再判断列之间要切的情况,取最小值即可。
时间复杂度$O(2^n imes n imes m)$
下面代码是E - Dividing Chocolate
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 int n, m, k; 6 int cnt[15][1005]; 7 char mp[15][1005]; 8 bool vis[1005]; 9 int cut[20], top; 10 int ans = 0x7f7f7f7f; 11 int cal(int x1, int x2, int y1, int y2) { 12 return cnt[x2][y2] - cnt[x2][y1 - 1] - cnt[x1 - 1][y2] + cnt[x1 - 1][y1 - 1]; 13 } 14 void dfs(int pos) { 15 cut[++top] = pos; 16 if (pos == n) { 17 memset(vis, 0, sizeof(vis)); 18 int sx, sy, lst = 0, tmp = 0; 19 for (int j = 1; j <= m; j++) { 20 for (int i = 1; i <= top; i++) { 21 sx = cut[i - 1] + 1, sy = lst + 1; 22 if (cal(sx, cut[i], sy, j) > k) { 23 if (lst == j - 1) { 24 top--; 25 return; 26 } else { 27 sy = j; 28 if (!vis[j - 1]) tmp++, vis[j - 1] = 1; 29 } 30 } 31 } 32 if (vis[j - 1]) lst = j - 1; 33 } 34 ans = min(ans, tmp + top - 1); 35 top--; 36 return; 37 } 38 for (int i = pos + 1; i <= n; i++) { 39 dfs(i); 40 } 41 top--; 42 } 43 int main() { 44 scanf("%d%d%d", &n, &m, &k); 45 for (int i = 1; i <= n; i++) { 46 scanf("%s", mp[i] + 1); 47 for (int j = 1; j <= m; j++) { 48 cnt[i][j] = cnt[i - 1][j] + cnt[i][j - 1] - cnt[i - 1][j - 1] + mp[i][j] - '0'; 49 } 50 } 51 for (int i = 1; i <= n; i++) { 52 dfs(i); 53 } 54 cout << ans; 55 return 0; 56 }
F - Knapsack for All Segments
题意
一个长度为n的数组,求任意[ L,R ]区间子序列和为S的总数的和。
题解
看到和为s,很容易想到背包,其实这就是一道01背包的变形。
考虑一组和为s的排列x1<x2<...<xk,所有[1,x1]内的L,[xk, n]之内的R都包含这组排列,所以它的贡献就是$x_1 imes (n-x_k+1)$。
设dp[i][j]表示枚举到第i个数,和为j的排列的第一个数的和。
显然dp[0][j]都是0。转移方程类似01背包,dp[i][j]=dp[i-1][j]+(j==a[i]?i:0)。
考虑如何统计答案,如果a[i]=s,ans+=i*(n-i+1);如果a[i]>s,ans+=dp[s-a[i]]*(n-i+1)。如果a[i]<s则不用更新。
当然类似于背包问题,这题也可以用滚动背包优化空间复杂度。
时间复杂度O(ns);空间复杂度O(n+s)
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 const long long N = 3010; 5 const long long S = 3010; 6 const long long mod = 998244353; 7 long long n, s; 8 long long a[N]; 9 long long dp[S]; 10 long long ans; 11 int main() { 12 scanf("%lld%lld", &n, &s); 13 for (long long i = 1; i <= n; i++) scanf("%lld", &a[i]); 14 for (long long i = 1; i <= n; i++) { 15 if (s == a[i]) { 16 ans += i * (n - i + 1) % mod; 17 ans %= mod; 18 } else if (s > a[i]) { 19 ans += dp[s - a[i]] * (n - i + 1) % mod; 20 ans %= mod; 21 } 22 for (long long j = s; j >= a[i]; j--) { 23 dp[j] += (dp[j - a[i]] + (j == a[i] ? i : 0)) % mod; 24 dp[j] %= mod; 25 } 26 } 27 printf("%lld", ans); 28 return 0; 29 }