以下为个人场上所提交答案,欢迎大家指出错误,后续会更正正确题解。
个人提交:25
说明: Mbps是M bit per second, MB是 M Byte,bit是比特,Byte是字节,1Byte=8bit, 200/8=25
个人提交:21844 (有误)
改正:1903
说明:只有每个位都为1、3、5、7之一才是题目所说的纯质数。还有一个条件当时没注意到,这个数本身也要是素数,应该先用埃氏筛法。
代码:
#include
#include
const int MAX_N = 20210605;
int num[MAX_N + 1];
int sum = 0;
void solve(int x) {
int isPrime = 1;
while(x > 0) {
int b = x%10; //b为x的个位数字
if(!(b == 2 || b == 3 || b == 5 || b == 7)) {
isPrime = 0;
break;
}
x = x/10;
}
if(isPrime) sum++;
}
int main() {
memset(num, 1, sizeof(num));
for(int i = 2; i < 20210605; i++) {
for(int j = 2; i*j <= 20210605; j++) {
num[i*j] = 0;
}
}
for(int i = 1; i <= 20210605; i++) {
if(num[i]) solve(i);
}
printf("%dn", sum);
return 0;
}
个人提交:977
说明:现场写日期类太麻烦,直接用excel搞数据,然后用c处理
先在excel中敲入这两格内容,第三格做个差,看看一共有多少天
得7699,从A1格往下拉7669行,得到从2001/1/1到2021/12/31的每一天
为了方便处理数据,把刚才不用的删掉,并且把表格另存为.csv文件
数据有了,直接全选复制,接下来用c/c++处理
#include
#include
#include
using namespace std;
int res = 0;
void solve(int x) {
for(int i = 0; i < 100; i++) {
if(i * i == x) res++;
}
}
int main() {
string line;
while(getline(cin, line)) {
int len = line.length();
int sum = 0;
for(int i = 0; i < len; i++) {
if(isdigit(line[i])) sum += line[i] - '0';
}
solve(sum);
cout << res << endl;
}
}
得出结果977
赛后听了老师讲,其实遍历所有日期也不难
#include
#include
int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int main() {
int cnt = 0;
for(int year = 2001; year <= 2021; year++) {
if(year%4==0 && year%100!=0 || year%400 == 0) days[2] = 29; //闰年29天
else days[2] = 28; //平年28天
for(int month = 1; month <=12; month++) {
for(int day = 1; day <= days[month]; day++) {
int total = 0;
int t = year;
while(t) {
total += t % 10;
t /= 10;
}
t = month;
while(t) {
total += t % 10;
t /= 10;
}
t = day;
while(t) {
total += t % 10;
t /= 10;
}
if((int)sqrt(total) * (int)sqrt(total) == total) {
cnt++;
printf("%d%02d%02d %2dn", year, month, day, total);
}
}
}
}
printf("%dn", cnt);
return 0;
}
个人提交:2667336761
说明:赌了一把完全二叉树,可惜不对
分析:
一颗具有n个结点的最小权二叉树,假设其左子树有j个结点,其右子树的结点数为n-j-1,那么左子树一定是具有j个结点的最小权二叉树,右子树也一定是具有n-j-1个结点的最小权二叉树。–最优子结构性质
所以可以用动态规划来求解,转移方程即题目中的W(v) = 1 + 2W(L) + 3W(R ) + (C(L))2 C(R )
设 d[i]=有i个结点的二叉树的最小权值
dp[n] = min{1 + 2dp[j] + 3dp[n-j-1] + j2(n-j-1)}, j = 0, 1, 2, …, n-1
#include
#include
using namespace std;
long long dp[2030];
int main() {
memset(dp, 0x3f, sizeof(dp)); //每个字节均为0x3f,即无穷大
dp[0] = 0;
for(int i = 1; i < 2025; i++) {
for(int j = 0; j <= i-1; j++) {
if((1 + 2*dp[j] + 3*dp[i-j-1] + j*j*(i-j-1)) < dp[i]) {
dp[i] = 1 + 2*dp[j] + 3*dp[i-j-1] + j*j*(i-j-1);
}
}
}
cout << dp[2021] << endl;
return 0;
}
代码:
#include
#include
#include
char s[110];
int main() {
scanf("%s", s);
for(int i = 0; i < strlen(s); i++)
printf("%c", toupper(s[i]));
return 0;
}
常规写法,过40%用例,评论区大佬说二分法,我在考场想到利用等差数列前n项和并根据R-L的值取分段函数,但是没来得及改,后期补上
#include
const int MAX_N = 200000000;
int a[MAX_N], sum = 0;
void solve() {
int maxn = 1, n = 1;
for(int i = 1; i < MAX_N; i++) {
a[i] = n++;
if(n > maxn) {
maxn++;
n=1;
}
}
}
int main() {
solve();
int T;
scanf("%d", &T);
for(int i = 0; i < T; i++) {
int l, r;
sum = 0;
scanf("%d%d", &l, &r);
for(int i = 0; l+i <= r; i++) {
sum += a[l+i];
}
printf("%dn", sum);
}
return 0;
}
当时的思路对了一部分
分析:
将数列划分为n项,其中第1项为1,第2项为1,2,第3项为1,2,3,…,第i项为1,2,…,i。显然前i项共包含了i(i+1)/2个数,最多需要不超过2*106项,即可表示数列的前1012个数。
定义f[i]表示第i项的和,sum[i]表示前i项的和.则
f[i] = i*(i+1)/2;
sum[i] = i*(i+1)*(i+2)/6;
定义函数cal(x)表示数列第1个数~第x个数的和,那么区间 [L, R] 的和就等于 cal(R ) - cal(L-1)。
而对于cal(x),我们设x在第i项第j个位置,那么可得:cal(x) = sum(i-1) + j(j+1)/2
如何求得i和j呢,随着i增大,i*(i+1)/2的值也增大,直到 i*(i+1)/2大于x,即可求出i,可以采用二分法求出i。已知i后,j可以一步算出,即 j = x - (i-1)(i)/2
时间复杂度为O(Tlogn)
#include
using namespace std;
long long sum(long long i) {
return i*(i+1)*(i+2)/6;
}
long long cal(long long x) {
long long left = 0, right = 2e6, i;
//找到最大的i,满足 i*(i+1)/2 <= x,即找到x的前一个项
while(left <= right) {
long long mid = left + right >> 1;
if(mid*(mid+1)/2 <= x) {
left = mid + 1; i = mid; //因为递增求i,所以当区间左边增大时赋值
} else {
right = mid - 1;
}
}
long long j = x-i*(i+1)/2;
return sum(i) + j*(j+1)/2;
}
int main() {
long long T = 1;
cin >> T;
while(T --) {
long long l, r;
cin >> l >> r;
cout << cal(r) - cal(l-1) << endl;
}
return 0;
}
没找到规律,硬写,过40%用例
评论区大佬说找循环节,后期补上
#include
#include
const int MAX_N = 200000000;
char s[MAX_N];
char s2[MAX_N];
int n, t;
void reverse() {
s2[0] = s[0];
for(int i = 1; i < n; i++) {
s2[i] = (s[i] - '0') ^ (s[i-1] - '0') + '0';
}
strncpy(s, s2, n);
}
int main() {
scanf("%d%d", &n, &t);
scanf("%s", s);
for(int i = 0; i < t; i++) reverse();
printf("%s", s);
return 0;
}
分析:
这道题并没有考察特别难的算法,其实多模拟一些输出可以找到循环节。
将题目案例进行16次变换
10110
11101
10011 // 10
11010
10111 // 1011
11100
10010
11011
10110 // 10110 进入下一次循环
11101
10011
11010
10111
11100
10010
11011
10110
10110
经过观察我们可以发现:
前 1 位数字在异或 a 次后等于输入本身
前 2 位数字在异或 2a 次后等于输入本身
前 3 为数字在异或 4a 次后等于输入本身
前 4 位数字在异或 4a 次后等于输入本身
前 5 位数字在异或 8a 次后等于输入本身
前 6 位数字在异或 8a 次后等于输入本身
前 7 位数字在异或 8a 次后等于输入本身
前 8 位数字在异或 8a 次后等于输入本身
前 9 位数字在异或 16a 次后等于输入本身
…
可以得出,前 n 位数字在变换 2k 次后恢复初始,最小的k满足n <= 2k
所以将题目中的t去掉循环部分,求剩余部分次数的变换,可以节省大量算法复杂度,题目最大的n为10000,最大的t为1e18,最多刚好进行 10000 * 10000 次运算,可以满足时间限制1s的要求。
#include
#include
long long s[10010];
long long n, t;
void printstr() {
for(int i = 0; i < n; i++) {
printf("%d", s[i]);
}
printf("n");
}
int main() {
scanf("%d%dn", &n, &t);
for(int i = 0; i < n; i++) {
s[i] = getchar() == '1' ? 1 : 0;
}
int len, x;
if(pow(2, (int)log(n)/log(2)) == n) {
// 2的n次幂 == log(n) / log(2) ,即log2(n)为整数
x = (int)(log(n)/log(2));
len = pow(2, x);
} else {
x = (int)(log(n)/log(2)) + 1; //上取整
len = pow(2, x);
}
t = t % len;
for(int i = 0; i < t; i++) {
for(int j = n; j > 0; j--) {
s[j] = s[j] ^ s[j-1];
}
}
printstr();
return 0;
}
不会dp,硬写过30%用例
#include
using namespace std;
int N, K, res = 0;
void solve(int x) {
int sum = 0;
while(x > 0) {
if(x % 2 == 1) sum++;
x = x/2;
}
if(sum == K) res++;
}
int