<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里。
本人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> using namespace std; typedef long long ll; const int maxn = 3e5 + 7; const int Inf = 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++) {
}
体会一下上面的代码,每次传进去一个 a[j],再压进vector里,形成一个第二维不定长的二维数组 vmp[i][j]。
下面介绍一下如何用vector建图,操作如下:
for(int i = 1; i <= n - 1; i++) {
}
以vmp[u].push_back(v)为例:第一次传进来一个u点,对应传进来一个v点压进vmp[u][0],第二次传进来一个u点,对应传进来一个v点压进vmp[u][1]…以此类推,理解了吧
就这两步,由于是无向的,相当于之前三数组建图声明的 build( )函数,
即建双向边。
建图操作就到此为止了,现在要理解为什么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要减一,是意味着根结点也满足搜索条件,但是不能把根结点算上。
本人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> using namespace std; const int 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"); return 0; } memset(cnt, 1, sizeof(cnt)); //先将自身算上 dfs(1); for(int i = 1; i <= n; i++) { if(!(cnt[i] & 1)) ans++; } cout << ans - 1 << endl; //减一是必须要减掉根结点 }