1. 程式人生 > 實用技巧 >csp2020_j(心路歷程 + 題解(T3未A) + 總結)

csp2020_j(心路歷程 + 題解(T3未A) + 總結)

比賽心路歷程

其實這次比賽筆者心態還是蠻好的。(或許是平時太弱爆0出鍋太多次了,以至於對於這種事看得很開???
剛開始先(聽gm的話)把所有的題目都過了一遍,然後就開始按照T1 \(\Rightarrow\) T2 \(\Rightarrow\) T4\(\Rightarrow\) T3\(\Rightarrow\)的順序開始了嘗試。

T1把所有大樣例都過了一遍之後,就去打T2,T2很明顯就是個排序嘛,但n的資料範圍為 100000,如果打暴力用sort的話也在\(O_{n^2 \times log^n_2}\)左右,於是考場上就開始想優化,突然想起了每位選手的分都\(0 \geqslant\)\(\geqslant 600\)

於是就非常自然的想到了桶排序。

在打了T2後,又改了改,過了所有的大樣例後就鬆了一口氣(現在想起我不是鬆了一口氣,而是差點在這兒斷氣,除了前面的200pts,後面都出鍋了)。然後就去肝T4,其實看到這個題面,我就很自然的想到了一道經典例題:數字三角形。但是還可以從下往上走的方式矇蔽住了我的雙眼,儘管第六感告訴我應該是道DP,(至少不是我最長路的做法),但筆者還是毅然決然的選擇用最長路去騙分(大致採用的堆優化的dijkstra)。雖然被第3組大樣例卡住了。但我還是去做T3去了。看到這道題的時候,完全沒有頭緒,而且時間也不多了。就瞎打了一個暴力打算騙取30pts。本來過掉了樣例1,但因為後來的魔改,加時間到。連樣例1都過不了了。

其實這次比賽還鬧了一個烏龍的:比賽前我的桌子上出現了一隻體型微小的蟑螂就暫且不論,更主要的是筆者腦殘用記事本開啟樣例,結果看不到換行,在詢問監考老師無果後考場上臨時總結出了雙擊看不到換行,單擊後再點檢視才有換行的經驗。

我好囉嗦呀

題解

T1

傳送門
其實就是一道純暴力,也是本次csp的打卡題。就不多贅述了

//考場上的程式碼加註釋(已去freopen)
#include <cstdio>
using namespace std;

long long n;

long long m_2(int x){    //計算2^x
	long long sum = 1;
	for(int i = 1; i <= x; i ++){
		sum *= 2;
	}
	return sum;
}

void my_read_int(int &x){  //未壓行的讀優
	int f = 1;
	char flag;
	flag = getchar();
	while(flag > '9' || flag < '0'){
		if(flag == '-'){
			f = -1;
		}
		flag = getchar();
	}
	while(flag >= '0' && flag <= '9'){
		x = x * 10;
		x += flag - '0';
		flag = getchar();
	}
	x *= f;
}

void my_read_longlong(long long &x){  //同上
	int f = 1;
	char flag;
	flag = getchar();
	while(flag > '9' || flag < '0'){
		if(flag == '-'){
			f = -1;
		}
		flag = getchar();
	}
	while(flag >= '0' && flag <= '9'){
		x = x * 10;
		x += flag - '0';
		flag = getchar();
	}
	x *= f;
}
int main() {
	my_read_longlong(n);
	long long flag;
	flag = n;
	int cnt = 0;
	if(n & 1){   //判斷是否為奇數
		printf("-1");
		return 0;
	}
	else{
		while(flag){   //計算在二進位制下最多有幾位
			cnt ++;
			flag >>= 1;
		}
	}
	flag = n;
//	int b;
//	scanf("%d", &b);
//	printf("%d\n", m_2(b));
	for(int i = cnt; i >= 1; i --){  //判斷在二進位制下某位是否為1
		long long m_i = m_2(i);
		if(flag >= m_i){
			printf("%d ", m_i);
			flag -= m_i;
		}
	}
	return 0;
}

T2

傳送門
其實在剛剛心路歷程也寫了,n的資料範圍為 100000,如果打暴力用sort的話也在\(O_{n^2 \times log^n_2}\)左右,於是考場上就開始想優化,突然想起了每位選手的分都\(0 \geqslant\)\(\geqslant 600\)於是就非常自然的想到了桶排序。。而且時間複雜的大概在\(O_{600n}\)。完全不會TLE(除非打鍋

//考場上的程式碼加註釋(已去freopen)
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e5 + 5;

int my_tong[1005];  

int a[maxn];
int n, m;

int my_max(int x, int y){
	return x > y ? x : y;   //自己寫的max函式時間複雜度要小一點
}

int Max = 0;

void my_read_int(int &x){   //日常讀優
	int f = 1;
	char flag;
	flag = getchar();
	while(flag > '9' || flag < '0'){
		if(flag == '-'){
			f = -1;
		}
		flag = getchar();
	}
	while(flag >= '0' && flag <= '9'){
		x = x * 10;
		x += flag - '0';
		flag = getchar();
	}
	x *= f;
}
int main(){
	memset(my_tong, 0, sizeof(my_tong));    //其實比賽用memset還不如暴力清空快,只是因為我這的陣列比較小才用的(最好還是不用)
	my_read_int(n);
	my_read_int(m);
	for(int i = 1; i <= n; i ++){
		my_read_int(a[i]);
		Max = my_max(a[i], Max);   //為了優化桶,迴圈中計算當前最大值
//		printf("%d\n", Max);
		my_tong[a[i]] ++;   //讀入每一個人的時候就丟進桶
		int num = my_max(1, i * m / 100);
//		printf("%d\n", num);
		int flag = 0, flag_num;
		for(int i = Max; i >= 0; i --){   //從最大值開始倒著累加知道人數夠了
			if(flag >= num){
				break;
			}
			flag += my_tong[i];
//			printf("%d ", my_tong[i]);
			flag_num = i;
		}
		printf("%d ", flag_num);   
	}
	return 0;
}

T3

因為筆者太弱了,肝了一個多小時的表示式建樹,加後面的判斷還沒有肝完(確實對於這一塊是空白的),所以先咕咕咕著吧,為給您帶來不太愉快的觀感而感到抱歉QAQ。

T4

其實比賽的時候想到了DP的(聯想到了數字三角形),但還是被可以向上走這一條件矇蔽住了雙眼,本來抱著騙騙至少普一就沒有問題了的(不純粹的)心態,瞎打了一個最長路(堆優化的dijkstra)。
結果還開大了MLE掉了(你谷和牛客都是)。

考完了參考了lym大巨佬的題解,才發現好妙啊~ %%%lym

用兩個dp陣列來儲存,一個最後一步向上,一個最後一步向下,就避免了重複的情況了。

ps:其實剛開始我看見lym大巨佬裡面第一列只能由上向下走還愣了一愣,畫了一下圖才理解

這樣路徑無論如何都會重複

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e3 + 5;

long long dp1[maxn][maxn];
long long dp2[maxn][maxn];
int a[maxn][maxn];
int n, m;
int main() {
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= n; i ++){
		for(int j = 1; j <= m; j ++){
			scanf("%d", &a[i][j]);
		}
	}
	memset(dp1, -0x3f3f3f3f, sizeof(dp1));
	memset(dp2, -0x3f3f3f3f, sizeof(dp2));
	dp1[1][1] = a[1][1]; //對於dp1[1][1]賦初值
	for(int i = 2; i <= n; i ++){
		dp1[i][1] = dp2[i][1] = dp1[i - 1][1] + a[i][1];   //對第一列賦初值,原理如上圖
	}
	for(int j = 2; j <= m; j ++){    //因為不同的狀態是由最後一步向上或向下決定的,所以第一重迴圈列舉j
		for(int i = 1; i <= n; i ++){  //處理從上往下和從左的情況
			dp1[i][j] = dp2[i][j] = max(dp1[i][j - 1], dp2[i][j - 1]) + a[i][j];
			dp1[i][j] = max(dp1[i - 1][j] + a[i][j], dp1[i][j]);
		}
		for(int i = n; i >= 1; i --){
			dp2[i][j] = max(dp2[i + 1][j] + a[i][j], dp2[i][j]);反向處理從下往上的情況
		}
	}
	printf("%lld", max(dp1[n][m], dp2[n][m]));
	return 0;
}

feedback

其實這場比賽給我的收穫還蠻大的,增長見識是次要的(雖然這是我參加的第一場正式的oi比賽),更重要的是讓我深刻的意識到了自己的不足以及和大巨佬們的差距,還讓筆者發現了自己在某些學習品質上的欠缺另一篇blog。雖然考了一個不太理想的成績,但給我帶來的收穫是分數無法估量的。