https://ac.nowcoder.com/acm/contest/91849
多年退役找工作選手膜拜眾出題大佬!!!出的題特別好!!!
A
尋找分開的若干個11..1
串,比如串001110001111
,就是111
和1111
。
首先,認為所有的0
,都是識別正確的,這些0
的下一個位置,都是人類。即0 next = Human
。
其次,對於11..1
串,從右到左,它們可以這樣設定(H
代表人類,B
代表機器):
0 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|
H | B | H | B | H | B |
11..1
串最後一個字元,也就是從右到左的第一個字元
,必定是B
,因為它識別錯了,下一個位置應該是人類,因為有前提:0 next = Human
,而只有機器人才會識別錯誤。然後逐漸交替,即B
H
B
H
B
… …。
實際做題的時候,大概感受一下輸入樣例是怎麼得到輸出的
,然後直接下手寫了。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define ULL unsigned long long
const LL mod_1=1e9+7;
const LL mod_2=998244353;
const double eps_1=1e-5;
const double eps_2=1e-10;
const int maxn=2e5+10;
string str;
int main()
{
int T, n, i, result, cnt;
cin>>T;
while (T--)
{
cin>>n>>str;
result = 0;
n--;
for (i=0; i<n; i++)
{
cnt = 0;
while (i<n && str[i]=='1')
{
i++;
cnt++;
}
result += (cnt+1)/2;
}
cout << result << endl;
}
return 0;
}
B
看榜單,這麼多人做對,而且時間比較短,猜測是比較短程式碼的題目。
我是稍微想了B沒思路,然後先做了C
(159
分時AC
),再折回來做B
。那時候時間就很緊張,剩下20
分鐘。
然後靜下心,把這個程式碼寫了。還遇到1e6
寫成1e5
的問題,還有多組測試樣例
下陣列
的問題。反正我就猜測有什麼問題,畢竟是最後1分鐘,把有可能發生錯誤的都試著改一下然後提交,甚至i-K
也試著改成i-K-1
和i-K+1
提交一下。運氣好,AC了。同時,感覺最近很多場線上比賽壓軸提交程式碼AC
,很爽!
因為是\(10^{6}\)複雜度,所以可以處理的演算法不多,我想過的有DP,ST演算法,感覺時間、空間都挺超的,還有尺取法,等等。我一開始想的,就是貪心,然後逐漸演化而成。
相鄰的兩個k倍數段(第k * j
個段和第k * (j + 1)
個段),至少要間隔k個數,然後貪心不了。後來想了一下,哦哦,其實對於某個數(位置為P
)作為第k * (j + 1)
個段的開頭,它就可以以位置1 ~ P-K
的任意一個數作為第k * j
個段的結尾。記錄位置為1 ~ P
作為第k * j
個段的結尾時的字首權值之和,然後取它們的最大值:\(\max_{i=1}^{P}(PreSum_{i})\)。每次P
位置+1
時可以O(1)處理。注意哈,我說的字首權值PreSum不是字首和,而是k倍數段中的數的數值之和。
對於第k * j
個段,若當前P
位置處於這個段,那麼P+1
位置可以選擇
新增\(a_{P + 1}\)這個數字,同時這個數仍然
處於第k * j
個段,仍然可以繼續擴充
。
關鍵程式碼:
start[i] = a[i];
value = max(value, start[i-K]);
start[i] = max(start[i], start[i-1] + a[i]);
start[i] = max(start[i], value + a[i]);
程式碼細節:
- \(10^{6}\)
- 陣列每次的初始化(不然WA)
K=1
無法分割段時的特殊處理(賽後再次測試得知,它是要單獨處理的,否則WA)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define ULL unsigned long long
const LL mod_1=1e9+7;
const LL mod_2=998244353;
const double eps_1=1e-5;
const double eps_2=1e-10;
const int maxn=1e6+10;
LL a[maxn], start[maxn];
int main()
{
LL T,n,K, sum, i, value;
memset(start, 0, sizeof(start));
scanf("%lld", &T);
while (T--)
{
scanf("%lld%lld", &n, &K);
for (i=1;i<=n;i++)
scanf("%lld", &a[i]);
for (i=1;i<=n;i++)
start[i] = 0;
value = 0;
for (i=1;i<=n;i++)
{
if (i<K)
continue;
start[i] = a[i];
value = max(value, start[i-K]);
start[i] = max(start[i], start[i-1] + a[i]);
start[i] = max(start[i], value + a[i]);
//start[1]~start[i-K]
}
if (K==1)
{
sum = 0;
for (i=1;i<=n;i++)
sum += a[i];
printf("%lld\n", sum);
continue;
}
sum = 0;
for (i=K;i<=n;i++)
sum = max(sum, start[i]);
printf("%lld\n", sum);
}
return 0;
}
C
看到這道題,我感受到了可能和這道題有點像,2018 “百度之星”程式設計大賽 - 初賽(A)度度熊學佇列,我那時的解法,這道題用list
資料結構,把編號為 v 的佇列接在
編號為 u 的佇列的最後面,可以做到O(1)
。實際上這道題的資料結構不適用這道題哈。
求\(p_{k}\)的數值,只考慮\(k\)這個位置的變化,那麼就要從後往前看
。比如1->3->2->4,1這個位置,從後往前看
(反過來看
),swap(1,3)
,swap(3,2)
,swap(2,4)
,最後是一開始的a[4]的數值在1這個位置。感覺遇到過這種這類的題不少次。
然後問題來了,swap(X,Y)
,就是當前處於X的數
和處於Y的數
交換位置。一個集合
和一個集合
交換位置,我想到的是像set
、list
、vector
這類的資料結構,都可以做到O(1)
處理。
然後,就是記錄和獲取work(L,R,K)
的數值。
實際上可能有很多個操作都在同一個位置,但是統一處理
降低了複雜度。否則,比如位置1
的數值經歷了m
次修改,n
個詢問都是問這個位置1
,複雜度就是\(O(n*m)\)了。
同時,有些資料結構,無法獲取R->L
,當到達位置L
時,要獲取結果然後結束(處理剛好
完成後,從某個資料結構中刪除
)。所以,選擇的資料結構需要有排序 / Top的功能。
實際操作中:
set
,記錄(L, Qth)
。Qth
是第Qth
個查詢。L
就是題目中的L
,(L,R) range
的L
。vector
,記錄反過來(從大到小),作為開始的R
,有哪些(L,R)
對是從某個特定的R
開始的。R
就是題目中的R
,(L,R) range
的R
。R
從大從小遍歷。R
從大從小遍歷,處理這個特定的R
的若干序列(K,L,Qth)
,(L,Qth)
加入set[K]
中。- 你並不知道對於特定的
(L,R,K)
,當L
處理完後,它在哪個set
中。因為set
有n
個(\(n \leq 10^6\)),你總不能依次遍歷n
個set
吧尋找吧。一個巧妙的處理點,對於已經在處理第I
個操作,在swap(set[ a[i] ], set[ b[i] ])
之前,對於第a[i]
和b[i]
個set
,可以把之前已經處理完的對
刪除
(erase)了。具體就看哪些對的L
值大於現在的I
。因為set
是自動排序的資料結構,所以這些要處理的(L,Qth)
實際上就在set
的最後面幾個。
程式碼實際上不長哈,就是有點耗腦。
因為很久沒寫set這類reverse
的程式碼了,寫起來很難受,這個地方卡了很長時間。刪除一個元素的時候,指標也會發生變化。比如rbegin
,erase(it)
,++it
,等等,反正就是很容易寫錯。這是用文心一言(ChatGPT更好哈)跑"set刪除一個元素,它的next指標發生變化"生成的結果。你需要先記錄next
指標,再刪除哈,或者每次刪除最大的元素並迴圈。我寫成it = --st[j].end()
,讓我有點難崩,應該有其它的寫法吧,也可以用auto
。
在 C++ 的 std::set 容器中,元素的儲存和訪問方式與連結串列(如單連結串列或雙連結串列)有很大的不同。std::set 是一種基於紅黑樹(一種自平衡二叉查詢樹)實現的關聯容器。因此,std::set 中的元素不是透過指標連結在一起的,而是透過節點在紅黑樹中的位置關係來組織。
當你從 std::set 中刪除一個元素時,以下幾點需要注意:
元素指標的變化:在 std::set 中,你通常不會直接操作元素的指標(如 next 指標),因為 std::set 的內部實現是基於節點的,而這些節點之間的連結(父節點、左子節點、右子節點)是由紅黑樹的性質來維護的。
紅黑樹的調整:當你刪除一個元素時,紅黑樹需要進行一系列旋轉和重新著色操作,以保持其平衡性和查詢效率。這些操作會改變樹的結構,但你不會直接看到或操作這些“指標”(即節點之間的連結)。
除錯的時候,不知道為什麼,我的VSCode
一直爆炸,每次除錯後,都得關閉重新開啟。我懷疑是set
的問題。就像,之前CodeBlocks
跑什麼發生錯誤後(比如段錯誤),這個軟體就得出點奇怪的問題,需要重新關閉開啟才能調教好。太難受啦!我是第一個遇到這個問題哈,我用VSCode
也是因為它的程式碼自動補全(這場基本沒有看到)和CPH外掛
(也沒太多用到,但稍微有點用)。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define ULL unsigned long long
const LL mod_1=1e9+7;
const LL mod_2=998244353;
const double eps_1=1e-5;
const double eps_2=1e-10;
const int maxn=1e6+10;
typedef pair<int, pair<int, int> > par;
vector<par> vec[maxn];
set<pair<int, int> > st[maxn];
set<pair<int, int> >::iterator it;
int result[maxn], a[maxn], b[maxn];
int n, m, q, L, R, K, x, y, z, i;
void handle(int j)
{
if (st[j].empty())
return;
while (!st[j].empty())
{
it = --st[j].end();
if (it->first > i)
{
result[it->second] = j;
st[j].erase(it);
}
else
return;
}
}
int main()
{
cin>>n>>m>>q;
for (i=1;i<=m;i++)
scanf("%d%d", &a[i], &b[i]);
for (i=1;i<=q;i++)
{
scanf("%d%d%d", &L, &R, &K);
vec[R].push_back(make_pair(K,make_pair(L, i)));
}
for (i=m;i>=1;i--) //L/R range
{
for (int j : {a[i], b[i]})
handle(j);
for (auto temp : vec[i])
{
x = temp.first;
y = temp.second.first;
z = temp.second.second;
st[x].insert(make_pair(y, z)); //L i
}
swap(st[ a[i] ], st[ b[i] ]);
}
i = 0;
for (int j = 1; j<=n; j++) // value range
handle(j);
for (i=1;i<=q;i++)
printf("%d\n", result[i]);
return 0;
}
P.S. 我覺得好的題的定義
我感覺這種很考思維邏輯,資料結構的題(我指的是前3題,後面沒看),ChatGPT很難生成正確的答案。其實AtCoder、CodeForces估計一直有這方面的嘗試和討論,出點很有創新的題目,而不是模板題。有一次我看到AtCoder歪榜
,你懂的字數字數
。
我的個人感悟巴拉巴拉
實力尚還在吧,多年後,一些資料結構有新的感悟,但是一些STL函式使用的確生疏了,寫得也相對比較慢,但是程式碼錯誤基本會比較少。雖然一開始有點事,後面環境也不是太安靜,但是他們都努力降低聲音了,set也很久沒寫了很生疏,但是最後20分鐘趕上了第三題的ac,最後兩分鐘趕上了第二題的ac。看了一下榜,三題AC倒3,這很正常,因為我大大高估了自己做第三題的時間(STL字數字數,太耗時了),第二題最後做的,罰時超大,而且想到的都交了,於是5WA。前100名,187分鐘的罰時,大概理想就是10(15) 30 (40) 90(120)吧,實際上挺難的,你還要考慮WA的次數。距離拿衣服還有很遠的地步,但是足夠了,很開心。話說,拿衣服就不能放寬到200名嗎
??!!!讓兄弟們混件衣服唄
!!!感覺參加這次比賽的同學都挺有水平,看它們牛客Ranking和顏色就知道了,普遍ACM銀金水平吧。我很多牛客比賽都是亂作的,還有人很菜,然後藍色,嗯?黑人問號?尊貴的藍色?嗯???