2018-6-18 ACM 刷題日記
<Codeforces 990C>
題意:
給n個只由 '(' 和 ')' 組成的串,問從這些串中選出兩個串,使他們括號匹配,能夠成多少個匹配串,自身和自身組合匹配也算。以題中第二個樣例說明:
輸入:2 ( ) ( )
輸出:4
解釋:輸入的兩個串 a,b 分別是 a: ( ),b: ( ),所以共四種連線方式,即 a->a,a->b,b->a,b->b
思路:
說實話,這個思路,就是一種只可意會不可言傳的感覺,我大概用我拙劣的語言,表述一下 T^T
我先對每個輸入進來的串,進行如下操作:
int ln = s.length();for(int j =0; j < ln; j ++){if(s[j]=='(') l++;else{if(l) l--;else r++;}
}
這裡的 l 代表,在輸入進來的這個串中,不能與自身右括號形成匹配的左括號的個數;
r 則代表,在輸入進來的這個串中,不能與自身左括號形成匹配的右括號的個數。
我已經進我最大努力表述了,自行腦補一下這裡有一個笑哭的表情。。。注意聽~
這樣每個串無法在自身形成匹配的左右括號數就分別找出來了。
然後進行如下兩步操作:
if(!l) R[r]++;if(!r) L[l]++;
以上兩步是核心程式碼,我盡我最大努力解釋清楚,
首先明確兩點,
(1)如果某個串裡 l != 0 && r != 0,那這個串沒法和任何一個串進行匹配
(2)有且僅有 x 個左括號不能在自身匹配的串, 它要想配對, 必須找有且僅有 x 個右括號不能在自身匹配的串
這也就是L[ ], R[ ]這兩個陣列的作用,如果 l == 0,那就意味著,這個串裡有且僅有 r 個右括號匹配不到 ( r 可以取0),這樣,它就必須找一個,有且僅有 r 個左括號匹配不到的的串,進行匹配。所以我在 l == 0 時,進行R[r]++,就是為了找到一個L[r]和當前的R[r]進行相乘再加入ans裡,L[ ],R[ ]陣列分別代表的值是僅有左括號不匹配,和僅有右括號不匹配的串的個數,而L[i], R[i]中的 i 值,則分別代表不匹配的左右括號各有幾個。r == 0 同理。
本人AC程式碼:
#include<cstdio>#include<cstdlib>#include<cstring>#include<cmath>#include<string>#include<queue>#include<set>#include<map>#include<stack>#include<ctime>#include<cctype>#include<vector>#include<iostream>#include<algorithm>usingnamespace std;typedeflonglong ll;constint maxn =3e5+7;constintInf=1e9+7;
pair <int,int> a[maxn];queue<int> qua;set<int> sst;vector<int> vect;map<int,int> mp;priority_queue<int,vector<int>, less<int>> qua1;priority_queue<int,vector<int>, greater<int>> qua2;int n;
string s;
ll L[maxn], R[maxn];//有pos個左括號不能在自身匹配的串, 它要想配對, 必須找有pos個右括號不能在自身匹配的串// 而L[pos]和R[pos]記錄的就是左右括號與自身不匹配的串的個數, 即ans += L[pos] * R[pos]
ll ans;int main(){
cin >> n;int l, r;//記錄每個串中左、右括號無法在該串自身內進行匹配的個數分別為多少for(int i =1; i <= n; i++){
cin >> s;
l = r =0;int ln = s.length();for(int j =0; j < ln; j++){if(s[j]=='(') l++;else{if(l) l--;else r++;}}if(!l) R[r]++;if(!r) L[l]++;}for(int i =1; i <= maxn; i++) ans += L[i]* R[i];
ans += L[0]* R[0];//自身就匹配的串的個數
cout << ans << endl;}
<Codeforces 982C>
題意:
有一個 n 個結點的樹,其中有n - 1條邊,問最多能刪除多少條邊,使得每個強連通分量的個數均為偶數。
思路:
首先考慮,n 的奇偶性,如果n是奇數,不管拆成多少個強聯通分量,都會至少有一個強聯通分量為奇數,強聯通分量,就可以理解為,一個子樹上結點的個數。如果 n 是偶數,那就直接 dfs 暴搜一遍,記錄對於每一個結點u,其父結點為v,u及其子樹的結點總數cnt[u],若cnt[u]為偶數, 就可以刪除邊<v, u>。
操作起來不是特別的繁瑣,取決於你建樹的方式,我之前不太會建樹,算了,不裝了,我之前是,壓根不會建樹,就只是照著板子抄就是pre[ ], other[ ], last[ ],三陣列建樹。下面介紹一個黑科技,利用vector建樹,我儘量解釋清楚,畢竟語文次的要命,別對我要求太高,邏輯姑且還是有點清晰的。
先說下vector用法,還沒詳細介紹過,宣告方式:vector <int> vmp[maxn],vector可以近似理解為一個二維陣列,
具體操作及意義:
int a[maxn];
for(int i = 1; i <= n; i++) {
cin >> t;
for(int j = 1; j <= t; i++) {
cin >> a[j];
vmp[i].push_back(a[j]);
}
}
體會一下上面的程式碼,每次傳進去一個 a[j],再壓進vector裡,形成一個第二維不定長的二維陣列 vmp[i][j]。
下面介紹一下如何用vector建圖,操作如下:
for(int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
vmp[u].push_back(v);
vmp[v].push_back(u);
}
以vmp[u].push_back(v)為例:第一次傳進來一個u點,對應傳進來一個v點壓進vmp[u][0],第二次傳進來一個u點,對應傳進來一個v點壓進vmp[u][1]…以此類推,理解了吧
就這兩步,由於是無向的,相當於之前三陣列建圖宣告的 build( )函式,
vmp[u].push_back(v); vmp[v].push_back(u); 這兩步就相當於,build(u,v); build(v,u);
即建雙向邊。
建圖操作就到此為止了,現在要理解為什麼dfs暴搜以及如何dfs,
通過推幾組樣例,比如上圖的樣例三,就會發現,記錄對於每一個結點u, 其父結點為v, u及其子樹的結點總數cnt[u],若cnt[u]為偶數, 就可以刪除邊<v, u>。可以理解為,將這個圖儘可能拆分成,幾組單獨的,只由一條邊和它所連線的兩個結點,所組成的強聯通分量。
那麼下面的任務就是 dfs 暴搜怎麼操作,具體步驟如下:
int dfs(int V){for(int i =0; i < vmp[V].size(); i++){int U = vmp[V][i];if(!vis[U]){
vis[U]=1;
cnt[V]+= dfs(U);}}return cnt[V];}
這時候就要考驗對vector的理解了,首先需要注意,vector下標必須從0開始,其次要理解,vmp[V][0]是父親結點,而每個vmp[V][i]是子結點。
搜過的點標記一下,然後搜到的子結點個數累加在cnt[V]上即可,注意對於所有的V,cnt[V]初值均要賦值為1,因為要把本身算上,最後對cnt[V]進行累加的ans要減1,是意味著根結點也滿足搜尋條件,但是不能把根結點算上。
本人AC程式碼:
#include<cstdio>#include<cstdlib>#include<cstring>#include<string>#include<cctype>#include<ctime>#include<string>#include<set>#include<map>#include<vector>#include<iostream>#include<algorithm>usingnamespace std;constint maxn =1e5+7;vector<int> vmp[maxn];int n;int ans;bool vis[maxn];int cnt[maxn];//記錄對於每一個結點u, 其父結點為v, u及其子樹的結點總數//若cnt[u]為偶數, 就可以刪除邊<v, u>int dfs(int V){for(int i =0; i < vmp[V].size(); i++){int U = vmp[V][i];if(!vis[U]){
vis[U]=1;
cnt[V]+= dfs(U);}}return cnt[V];}int main(){int u, v;
cin >> n;for(int i =1; i <= n -1; i++){
cin >> u >> v;
vmp[u].push_back(v);
vmp[v].push_back(u);}if(n &1){
puts("-1");return0;}
memset(cnt,1,sizeof(cnt));//先將自身算上
dfs(1);for(int i =1; i <= n; i++){if(!(cnt[i]&1)) ans++;}
cout << ans -1<< endl;//減一是必須要減掉根結點}