Category Archives: hihoCoder

hihoCoder 1123-好配对

hihoCoder 1123-好配对 #1123 : 好配对 时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 给定两个序列a和b,每个序列中可能含有重复的数字。 一个配对(i,j)是一个好配对当从第一个序列中选出一个数ai,再从第二个序列中选出一个数bj且满足ai>bj。 给出两个序列,问存在多少个好配对。 输入 输入包含多组数据,数据第一行一个整数T,表示数据组数。(T<=5) 每组数据第一行包含两个整数n和m。(0<n,m<=$$10^5$$) 接下来n行,每行两个整数x和y,表示在第一个序列中有y个x。 接下来m行,每行两个整数x和y,表示在第二个序列中有y个x。(0<x<=$$10^9$$,0<y<=$$10^4$$) 输出 对于每组数据,输出一行一个整数,表示好配对的数量 样例输入 1 2 2 3 2 4 1 3 1 2 3 样例输出 10


此题最先想到的解法是类似桶排序的思路,用空间来换时间,但是看到x的范围是$$10^9$$,显然不行,于是牺牲时间来换空间,构造: [cpp] typedef struct Ele { int value; int num; bool operator < (const Ele& e) const { return value < e.value; } }; [/cpp] 依次输入两个序列,并按value从小到大排序;两个序列从头开始遍历,计算好配对的个数。 完整代码如下: [cpp] #include<iostream> #include<cstdio> #include<vector> #include<algorithm> using namespace std; typedef struct Ele { int value; int num; bool operator < (const Ele& e) const { return value < e.value; } }; vector<Ele> first_seq, second_seq; int main() { int t,m,n,x,y; long long ans; scanf("%d", &t); while (t–) { scanf("%d %d", &n, &m); first_seq.clear(); first_seq.resize(n); second_seq.clear(); second_seq.resize(m); for (int i = 0; i < n; i++) scanf("%d %d", &first_seq[i].value, &first_seq[i].num); for (int i = 0; i < m; i++) scanf("%d %d", &second_seq[i].value, &second_seq[i].num); sort(first_seq.begin(), first_seq.end()); sort(second_seq.begin(), second_seq.end()); ans = 0; int i = 0, j = 0; long long sum2 = 0; while (i < n && j < m) { while (j < m && second_seq[j].value < first_seq[i].value) { sum2 += second_seq[j].num; j++; } ans += (first_seq[i].num*sum2); i++; } while (i < n) { ans += (first_seq[i].num*sum2); i++; } printf("%lldn", ans); } return 0; } [/cpp] 本代码提交AC,用时193MS,内存11MB。]]>

hihoCoder week 59-1-Performance Log

hihoCoder week 59-1-Performance Log 题目1 : Performance Log 时间限制:8000ms 单点时限:1000ms 内存限制:256MB 描述 You are given a txt file, which is performance logs of a single-threaded program. Each line has three columns as follow: [Function Name] [TimeStamp] [Action] [FunctionName] is a string of length between 1~255 [TimeStamp] format is hh:mm:ss Valid values for “Action” column are START or END, marking the start or end of a function call. Each function will only be called once. Output the depth-first traversal result of the call graph with the total time of each function call. However, sometimes the performance log isn’t correct and at that time you just need to output “Incorrect performance log”. 输入 The input only contains 1 case, first line is a positive number N representing the number of logs(1 <= N <= 20000), then there are N lines in next, each line is the log info containing [Function Name] [TimeStamp] [Action], [Function Name] is a string, you can assume the [Function Name] is distinct and the length between 1~255. 输出 Output the depth-first traversal result of the call graph with the total time of each function call for the correct performance, or output “Incorrect performance log”. 提示 A call graph is a directed graph that represents calling relationships between subroutines in a computer program. Call graph for the sample input is shown as below: Another sample test case.

Sample Input Sample Output
8 FuncA 00:00:01 START FuncB 00:00:02 START FuncC 00:00:03 START FuncA 00:00:04 END FuncB 00:00:05 END FuncD 00:00:06 START FuncD 00:00:07 END FuncC 00:00:08 END Incorrect performance log
样例输入 8 FuncA 00:00:01 START FuncB 00:00:02 START FuncC 00:00:03 START FuncC 00:00:04 END FuncB 00:00:05 END FuncD 00:00:06 START FuncD 00:00:07 END FuncA 00:00:08 END 样例输出 FuncA 00:00:07 FuncB 00:00:03 FuncC 00:00:01 FuncD 00:00:01
本题要判断一个程序的函数调用关系是否合法。需要考虑一下几点: 1. 序列时间必须递增(经测试无需严格递增) 2. START必须在END的前面 3. 函数调用关系不能交叉,比如ABCBCA就不行,因为B调用了C,但C没结束B就结束了,对于单线程程序,显然不行 4. 因为是单线程程序,主函数A的开始和结束要是log的第一条和最后一条。 对于第3点,可以借助堆栈完成。完整代码如下: [cpp] #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include<stack> #include<map> #include<vector> #include<cstdio> using namespace std; typedef struct one_log { string name; int sec; }; void sec2str(int sec) { int hh = sec / 3600; int mm = (sec – hh * 3600) / 60; int ss = sec – hh * 3600 – mm * 60; printf("%02d:%02d:%02dn", hh, mm, ss); } int main() { freopen("input.txt", "r", stdin); int n; stack<one_log> logs; string function_name, start_end,first_name,last_name; int hh, mm, ss; char tmp_c; vector<string> seq; map<string, int> ans; bool is_correct = true; scanf("%d", &n); for (int t = 0; t < n;t++) { cin >> function_name >> hh >> tmp_c >> mm >> tmp_c >> ss >> start_end; if (t == 0) first_name = function_name; if (t == n – 1) last_name = function_name; if (!is_correct) continue; one_log l; l.name = function_name; l.sec = hh * 3600 + mm * 60 + ss; if (start_end == "START") { logs.push(l); seq.push_back(l.name); } else { one_log tmp = logs.top(); //if (l.sec <= tmp.sec) if (l.sec < tmp.sec)//不是“严格”递增的。 { is_correct = false; continue; } if (tmp.name == l.name) { ans[l.name] = l.sec – tmp.sec; logs.pop(); } else logs.push(l); } } if (!is_correct||!logs.empty()||first_name!=last_name) printf("Incorrect performance logn"); else { for (int i = 0; i < seq.size(); i++) { printf("%s ", seq[i].c_str()); sec2str(ans[seq[i]]); } } return 0; } [/cpp] 本代码提交AC,用时61MS,内存2MB。]]>

hihoCoder week 58-1-Beautiful String

hihoCoder week 58-1-Beautiful String 题目1 : Beautiful String 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 We say a string is beautiful if it has the equal amount of 3 or more continuous letters (in increasing order.) Here are some example of valid beautiful strings: “abc”, “cde”, “aabbcc”, “aaabbbccc”. Here are some example of invalid beautiful strings: “abd”, “cba”, “aabbc”, “zab”. Given a string of alphabets containing only lowercase alphabets (a-z), output “YES” if the string contains a beautiful sub-string, otherwise output “NO”. 输入 The first line contains an integer number between 1 and 10, indicating how many test cases are followed. For each test case: First line is the number of letters in the string; Second line is the string. String length is less than 10MB. 输出 For each test case, output a single line “YES”/”NO” to tell if the string contains a beautiful sub-string. 提示 Huge input. Slow IO method such as Scanner in Java may get TLE. 样例输入 4 3 abc 4 aaab 6 abccde 3 abb 样例输出 YES NO YES NO


本题考察字符串的基本知识。题目给出了漂亮字符串的定义,形如aa…bb…cc,保证至少3个连续升序的字符串,并且每个字符的个数相同。仔细观察可以发现,虽然aaaabbccc不是漂亮字符串,但其子串aabbcc是漂亮的,所以只要保证b的数量小于等于a和c的数量即可。 所以解法就是遍历字符串,计算每个字符重复出现的次数,判断连续3种字符是否为连续升序,且满足中间字符数量大于两边。算法复杂度为O(N)。 完整代码如下: [cpp] #include<iostream> #include<cstdio> #include<string> using namespace std; const int kMaxN = 10 * 1024 * 1024 + 5; char line[kMaxN]; int n; char same_letter[kMaxN]; int same_length[kMaxN]; bool check(int k) { if ((same_letter[k] – 1 == same_letter[k – 1]) && (same_letter[k – 1] – 1 == same_letter[k – 2])) if (same_length[k – 1] <= same_length[k] && same_length[k – 1] <= same_length[k – 2]) return true; return false; } bool IsBeautiful() { if (n < 3) return false; else { int i = 0,k=0; while (i < n) { int j = i + 1; while (j < n && line[j] == line[i]) j++; same_letter[k] = line[i]; same_length[k] = j – i; if (k >= 2) { if (check(k)) return true; } k++; i = j; } return false; } } int main() { int t; scanf("%d", &t); while (t–) { scanf("%d", &n); scanf("%s", line); if (IsBeautiful()) printf("YESn"); else printf("NOn"); } return 0; } [/cpp] 本代码提交AC,用时293MS,内存37MB。]]>

hihoCoder week 55-1-连通性·四

hihoCoder week 55-1-连通性·四 题目1 : 连通性·四 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho从约翰家回到学校时,网络所的老师又找到了小Hi和小Ho。 老师告诉小Hi和小Ho:之前的分组出了点问题,当服务器(上次是连接)发生宕机的时候,在同一组的服务器有可能连接不上,所以他们希望重新进行一次分组。这一次老师希望对连接进行分组,并把一个组内的所有连接关联的服务器也视为这个组内的服务器(注意一个服务器可能属于多个组)。 这一次的条件是对于同一个组满足:当组内任意一个服务器宕机之后,不会影响组内其他服务器的连通性。在满足以上条件下,每个组内的边数量越多越好。 比如下面这个例子,一共有6个服务器和7条连接: 其中包含3个组,分别为{(1,2),(2,3),(3,1)},{(4,5),(5,6),(4,6)},{(3,4)}。对{(1,2),(2,3),(3,1)}而言,和该组边相关联的有{1,2,3}三个服务器:当1宕机后,仍然有2-3可以连接2和3;当2宕机后,仍然有1-3可以连接1和3;当3宕机后,仍然有1-2可以连接1和2。 老师把整个网络的情况告诉了小Hi和小Ho,希望小Hi和小Ho统计一下一共有多少个分组。 提示:点的双连通分量 输入 第1行:2个正整数,N,M。表示点的数量N,边的数量M。1≤N≤20,000, 1≤M≤100,000 第2..M+1行:2个正整数,u,v。第i+1行表示存在一条边(u,v),编号为i,连接了u,v两台服务器。1≤u<v≤N 保证输入所有点之间至少有一条连通路径。 输出 第1行:1个整数,表示该网络的连接组数。 第2行:M个整数,第i个数表示第i条连接所属组内,编号最小的连接的编号。比如分为{(1,2)[1],(2,3)[3],(3,1)[2]},{(4,5)[5],(5,6)[7],(4,6)[6]},{(3,4)[4]},方括号内表示编号,则输出{1,1,1,4,5,5,5}。 样例输入 6 7 1 2 1 3 2 3 3 4 4 5 4 6 5 6 样例输出 3 1 1 1 4 5 5 5


这一题求点的双连通分量,结果是边集;上一题求了边的双连通分量,结果是点集。 点的双连通分量是指子图中去掉一个点之后还连通,求解方法还是Tarjan,和之前的题目类似。需要注意的是,提示中的伪代码有错误,有好几个地方没有将边添加到堆栈中,正确的伪代码在这里。 本题要求输出连通分量中边的最小编号,所以输入的时候要记录边的编号,并且保证当弹出堆栈时,能快速知道这是哪一条边,简单的方法是将边(u,v)映射成一个整数x,因为u,v≤20,000,所以可以令hash=max(u,v)*100000+min(u,v)。 因为点的双连通分量数等于割点数量加1,如果DFS求到有2个割点,实际有3个连通分量,所以当DFS结束时,所有没有标记的边就是最后一个连通分量。 完整代码如下: [cpp] #include<iostream> #include<cstdio> #include<vector> #include<map> #include<stack> #include<algorithm> using namespace std; const int kMaxN = 20005; const int kMaxM = 100005; map<int, int> edge_code; map<int, int> ans; vector<vector<int> > graph; vector<int> edges; stack<int> stk; bool visit[kMaxN]; int low[kMaxN]; int dfn[kMaxN]; int parent[kMaxN]; int n, m; int points=0; int HashEdge(int x, int y) { if (x > y) return x * 100000 + y; else return y * 100000 + x; } void dfs(int u) { //记录dfs遍历次序 static int counter = 0; //记录节点u的子树数 int children = 0; visit[u] = true; //初始化dfn与low dfn[u] = low[u] = ++counter; for (int i = 0; i < graph[u].size(); i++) { int v = graph[u][i]; int e = HashEdge(u, v); if (ans.find(e)!=ans.end()) continue; //节点v未被访问,则(u,v)为树边 if (!visit[v]) { children++; parent[v] = u; stk.push(e); dfs(v); low[u] = min(low[u], low[v]); //case (1) if (parent[u] == 0 && children > 1) { points++; int tmp = -1, id = kMaxM; vector<int> tmp_edges; do{ tmp = stk.top(); stk.pop(); tmp_edges.push_back(tmp); if (edge_code[tmp] < id) id = edge_code[tmp]; } while (tmp != e); for (int i = 0; i < tmp_edges.size(); i++) ans.insert(make_pair(tmp_edges[i], id)); } //case (2) if (parent[u] != 0 && low[v] >= dfn[u]) { points++; int tmp = -1, id = kMaxM; vector<int> tmp_edges; do{ tmp = stk.top(); stk.pop(); tmp_edges.push_back(tmp); if (edge_code[tmp] < id) id = edge_code[tmp]; } while (tmp != e); for (int i = 0; i < tmp_edges.size(); i++) ans.insert(make_pair(tmp_edges[i], id)); } } //节点v已访问,则(u,v)为回边 else if (v != parent[u]) { stk.push(e); low[u] = min(low[u], dfn[v]); } } } int main() { scanf("%d %d", &n, &m); graph.resize(n + 1); int u, v,tmp; for (int i = 1; i <= m; i++) { scanf("%d %d", &u, &v); graph[u].push_back(v); graph[v].push_back(u); tmp = HashEdge(u, v); edges.push_back(tmp); edge_code.insert(make_pair(tmp, i)); } dfs(1); //求解最后一个连通分量 int max_tmp = kMaxM; for (int i = 1; i <= m; i++) { if (ans.find(edges[i – 1]) == ans.end()) if (edge_code[edges[i – 1]] < max_tmp) max_tmp = edge_code[edges[i – 1]]; } printf("%dn", points + 1); for (int i = 1; i <= m; i++) printf("%d ", ans[edges[i – 1]] == 0 ? max_tmp : ans[edges[i – 1]]); return 0; } [/cpp] 本代码提交AC,用时240MS,内存10MB。]]>

hihoCoder 1185-连通性·三

hihoCoder 1185-连通性·三 #1185 : 连通性·三 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 暑假到了!!小Hi和小Ho为了体验生活,来到了住在大草原的约翰家。今天一大早,约翰因为有事要出去,就拜托小Hi和小Ho忙帮放牧。 约翰家一共有N个草场,每个草场有容量为W[i]的牧草,N个草场之间有M条单向的路径。 小Hi和小Ho需要将牛羊群赶到草场上,当他们吃完一个草场牧草后,继续前往其他草场。当没有可以到达的草场或是能够到达的草场都已经被吃光了之后,小hi和小Ho就把牛羊群赶回家。 一开始小Hi和小Ho在1号草场,在回家之前,牛羊群最多能吃掉多少牧草? 举个例子: 图中每个点表示一个草场,上部分数字表示编号,下部分表示草场的牧草数量w。 在1吃完草之后,小Hi和小Ho可以选择把牛羊群赶到2或者3,假设小Hi和小Ho把牛羊群赶到2: 吃完草场2之后,只能到草场4,当4吃完后没有可以到达的草场,所以小Hi和小Ho就把牛羊群赶回家。 若选择从1到3,则可以到达5,6: 选择5的话,吃完之后只能直接回家。若选择6,还可以再通过6回到3,再到5。 所以该图可以选择的路线有3条: 1->2->4                      total: 11 1->3->5                      total: 9 1->3->6->3->5           total: 13 所以最多能够吃到的牧草数量为13。 本题改编自USACO月赛金组 提示:强连通分量 输入 第1行:2个正整数,N,M。表示点的数量N,边的数量M。1≤N≤20,000, 1≤M≤100,000 第2行:N个正整数,第i个整数表示第i个牧场的草量w[i]。1≤w[i]≤100,000 第3..M+2行:2个正整数,u,v。表示存在一条从u到v的单向路径。1≤u,v≤N 输出 第1行:1个整数,最多能够吃到的牧草数量。 样例输入 6 6 2 4 3 5 4 4 1 2 2 4 1 3 3 5 3 6 6 3 样例输出 13


本题将图换成了有向图。首先求解有向图中的强连通分量,强连通分量是指有向图的子图中,两两点之间互相可达,求解方法和之前两道题的方法类似,还是用Tarjan算法,可以参考BYVoid的博客。 求解到所有强连通分量之后,将每个强连通分量缩成一个点,构成一个新的有向图,缩点的草量等于该强连通分量草量之和。 假设原图点1在新图中的缩点编号为u,则得到一个新的有向图之后,问题就转换为在新图中从点u开始遍历,求经过路径的草量之和最大是多少。这一步可以用常规DFS方法求解,也可以用提示中的拓扑排序求解,我觉得DFS更好理解一点。 完整代码如下: [cpp] #include<iostream> #include<cstdio> #include<queue> #include<algorithm> #include<vector> #include<stack> using namespace std; const int kMaxN = 20005; int n, m,new_n=0; stack<int> stk; vector<vector<int>> graph; vector<vector<int>> new_graph; int w[kMaxN]; int new_w[kMaxN]; int dfn[kMaxN];//DFS时的序号,如果!=0说明已经被访问过了 int low[kMaxN]; bool in_stack[kMaxN]; int belong[kMaxN]; int grass[kMaxN]; int in_degree[kMaxN]; bool visit[kMaxN]; int rs = 0; void Tarjan(int u) { //记录dfs遍历次序 static int counter = 0; stk.push(u); in_stack[u] = true; //初始化dfn与low dfn[u] = low[u] = ++counter; for (int i = 0; i < graph[u].size(); i++) { int v = graph[u][i]; //节点v未被访问 if (dfn[v] == 0) { Tarjan(v); low[u] = min(low[u], low[v]); } //节点v已访问,则(u,v)为回边 else if (in_stack[v]) { low[u] = min(low[u], dfn[v]); } } if (dfn[u] == low[u]) { new_n++; int temp; do{ temp = stk.top(); stk.pop(); new_w[new_n] += w[temp]; in_stack[temp] = false; belong[temp] = new_n; } while (temp != u); } } void RebuildGraph() { //重建邻接矩阵 new_graph.resize(new_n + 1); for (int i = 1; i <= n; i++) { for (int j = 0; j < graph[i].size(); j++) { if (belong[i] != belong[graph[i][j]]) { new_graph[belong[i]].push_back(belong[graph[i][j]]);//有边 in_degree[belong[graph[i][j]]]++; } } } } int TopSort() { queue<int> zero_in_degree; for (int i = 1; i <= new_n; i++) if (in_degree[i] == 0) zero_in_degree.push(i); int ans = 0; while (!zero_in_degree.empty()) { int node = zero_in_degree.front(); zero_in_degree.pop(); if (visit[node])//点belong[1]能访问到的点才计算,否则这步不执行,grass[node]为0 grass[node] += new_w[node]; if (grass[node] > ans) ans = grass[node]; for (int i = 0; i < new_graph[node].size(); i++) { int node2 = new_graph[node][i]; if (grass[node]>grass[node2]) grass[node2] = grass[node]; if (–in_degree[node2] == 0) zero_in_degree.push(node2); } } return ans; } //记录哪些点是belong[1]能访问到的 void Visit() { queue<int> q; q.push(belong[1]); while (!q.empty()) { int f = q.front(); q.pop(); visit[f] = true; for (int i = 0; i < new_graph[f].size(); i++) q.push(new_graph[f][i]); } } void DFS(int u,int g) { if (rs < g) rs = g; for (int i = 0; i < new_graph[u].size(); i++) DFS(new_graph[u][i], g + new_w[new_graph[u][i]]); } int main() { //freopen("input.txt", "r", stdin); scanf("%d %d", &n, &m); graph.resize(n + 1); for (int i = 1; i <= n; i++) scanf("%d", &w[i]); int u, v; while (m–) { scanf("%d %d", &u, &v); graph[u].push_back(v); } for (int i = 1; i <= n;i++)//每个点都要尝试一次 if (dfn[i]==0) Tarjan(i); RebuildGraph(); Visit(); printf("%dn", TopSort()); //深搜的方法也可以 //DFS(belong[1], new_w[belong[1]]); //printf("%dn", rs); return 0; } [/cpp] 本代码提交AC,用时57MS,内存3MB。 有两点需要注意:第一,因为有些点可能是1到达不了的,这样Tarjan(1)就不能得到所有的强连通分量,所以需要for循环尝试每一个点;第二,在拓扑排序的时候,如果某个缩点是belong[1]无法访问的,则其草量为0。]]>

hihoCoder 1184-连通性二·边的双连通分量

hihoCoder 1184-连通性二·边的双连通分量 #1184 : 连通性二·边的双连通分量 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 在基本的网络搭建完成后,学校为了方便管理还需要对所有的服务器进行编组,网络所的老师找到了小Hi和小Ho,希望他俩帮忙。 老师告诉小Hi和小Ho:根据现在网络的情况,我们要将服务器进行分组,对于同一个组的服务器,应当满足:当组内任意一个连接断开之后,不会影响组内服务器的连通性。在满足以上条件下,每个组内的服务器数量越多越好。 比如下面这个例子,一共有6个服务器和7条连接: 其中包含2个组,分别为{1,2,3},{4,5,6}。对{1,2,3}而言,当1-2断开后,仍然有1-3-2可以连接1和2;当2-3断开后,仍然有2-1-3可以连接2和3;当1-3断开后,仍然有1-2-3可以连接1和3。{4,5,6}这组也是一样。 老师把整个网络的情况告诉了小Hi和小Ho,小Hi和小Ho要计算出每一台服务器的分组信息。 提示:边的双连通分量 输入 第1行:2个正整数,N,M。表示点的数量N,边的数量M。1≤N≤20,000, 1≤M≤100,000 第2..M+1行:2个正整数,u,v。表示存在一条边(u,v),连接了u,v两台服务器。1≤u<v≤N 保证输入所有点之间至少有一条连通路径。 输出 第1行:1个整数,表示该网络的服务器组数。 第2行:N个整数,第i个数表示第i个服务器所属组内,编号最小的服务器的编号。比如分为{1,2,3},{4,5,6},则输出{1,1,1,4,4,4};若分为{1,4,5},{2,3,6}则输出{1,2,2,1,1,2} 样例输入 6 7 1 2 1 3 2 3 3 4 4 5 4 6 5 6 样例输出 2 1 1 1 4 4 4


边的双连通分量是指无向图的一个子图,当删除其中任意一条边后,不改变图内点的连通性。在hihoCoder week 52-1-连通性·一中,我们求过一个图的桥,当删除所有桥之后,图肯定不连通了,此时剩下的每一个区域就是我们要求的边的双连通分量。 所以简单的方法就是先用上一题的方法求出桥,然后删除所有桥,进行DFS遍历求出边的双连通分量。但是提示给出了一个更巧妙的方法,在求桥的过程中,使用一个堆栈保存遍历过的点,当low[u]==dfn[u]时,u点通过回边追溯到的最早节点是其本身,说明u和parent[u]的同伙只有唯一的一条边(parent[u],u)相连,且这条边是桥,那么此时栈内在u之前入栈的点和u被该桥分割开,也就是说从u到栈顶的所有元素是一组。 完整代码如下: [cpp] #include<iostream> #include<cstdio> #include<stack> #include<algorithm> #include<vector> using namespace std; const int kMaxN = 20005; int n, m; vector<vector<int>> graph; stack<int> stk; int visit[kMaxN]; int dfn[kMaxN]; int low[kMaxN]; int parent[kMaxN]; int belong[kMaxN]; int bridge_num=0; void dfs(int u) { //记录dfs遍历次序 static int counter = 0; //记录节点u的子树数 int children = 0; visit[u] = 1; //初始化dfn与low dfn[u] = low[u] = ++counter; //将u加入栈 stk.push(u); for (int i = 0; i < graph[u].size(); i++) { int v = graph[u][i]; //节点v未被访问,则(u,v)为树边 if (visit[v] == 0) { children++; parent[v] = u; dfs(v); low[u] = min(low[u], low[v]); //bridge if (low[v] > dfn[u]) { bridge_num++; } } //节点v已访问,则(u,v)为回边 else if (v != parent[u]) { low[u] = min(low[u], dfn[v]); } } if (low[u] == dfn[u]) { int min_id = u; vector<int> member; while (stk.top() != u) { min_id = min(min_id, stk.top()); member.push_back(stk.top()); stk.pop(); } member.push_back(u); stk.pop(); for (int i = 0; i < member.size(); i++) belong[member[i]] = min_id; } } int main() { //freopen("input.txt", "r",stdin); scanf("%d %d", &n, &m); graph.resize(n + 1); int u, v; while (m–) { scanf("%d %d", &u, &v); graph[u].push_back(v); graph[v].push_back(u); } dfs(1); printf("%dn", bridge_num+1); for (int i = 1; i <= n; i++) printf("%d ", belong[i]); return 0; } [/cpp] 本代码提交AC,用时69MS,内存5MB。 有一点需要提醒,题目要求同一个联通分量用内部最小结点来表示,这里不能想当然的认为当low[u]=dfn[u]时,最小节点是u,万一是下图,2和3比6后进入堆栈,也就是2和3在堆栈中的位置在6的上面,当回溯到6号节点时,有low[6]==dfn[6],但是右边的联通分量中最小节点显然是2,所以还是老老实实遍历求最小值。 ]]>

hihoCoder week 52-1-连通性·一

hihoCoder week 52-1-连通性·一 题目1 : 连通性·一 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 还记得上次小Hi和小Ho学校被黑客攻击的事情么,那一次攻击最后造成了学校网络数据的丢失。为了避免再次出现这样的情况,学校决定对校园网络进行重新设计。 学校现在一共拥有N台服务器(编号1..N)以及M条连接,保证了任意两台服务器之间都能够通过连接直接或者间接的数据通讯。 当发生黑客攻击时,学校会立刻切断网络中的一条连接或是立刻关闭一台服务器,使得整个网络被隔离成两个独立的部分。 举个例子,对于以下的网络: 每两个点之间至少有一条路径连通,当切断边(3,4)的时候,可以发现,整个网络被隔离为{1,2,3},{4,5,6}两个部分: 若关闭服务器3,则整个网络被隔离为{1,2},{4,5,6}两个部分: 小Hi和小Ho想要知道,在学校的网络中有哪些连接和哪些点被关闭后,能够使得整个网络被隔离为两个部分。 在上面的例子中,满足条件的有边(3,4),点3和点4。 提示:割边&割点 输入 第1行:2个正整数,N,M。表示点的数量N,边的数量M。1≤N≤20,000, 1≤M≤100,000 第2..M+1行:2个正整数,u,v。表示存在一条边(u,v),连接了u,v两台服务器。1≤u<v≤N 保证输入所有点之间至少有一条连通路径。 输出 第1行:若干整数,用空格隔开,表示满足要求的服务器编号。从小到大排列。若没有满足要求的点,该行输出Null 第2..k行:每行2个整数,(u,v)表示满足要求的边,u<v。所有边根据u的大小排序,u小的排在前,当u相同时,v小的排在前面。若没有满足要求的边,则不输出 样例输入 6 7 1 2 1 3 2 3 3 4 4 5 4 6 5 6 样例输出 3 4 3 4


本题要求一个连通图的割边和割点,使用Tarjan算法,提示已经把dfs主要代码写了,完善一下即可。 主要要理解上面的递推公式。首先理解dfn和low的含义,dfn[u]记录节点u在DFS过程中被遍历到的次序号,low[u]记录节点u或u的子树通过非父子边追溯到最早的祖先节点(即DFS次序号最小)。 上图的第一个式子,当(u,v)为树边时,low[u]表明u能追溯到的最早节点,low[v]表明u的子树(v就是u的子树的根)能追溯到的最早节点,所以根据low[u]的定义有low[u]=min(low[u],low[v])。 上图的第二个式子,当(u,v)为回边时,因为low[u]的定义就是u通过非父子边(回边)追溯最早的节点,所以既然(u,v)为回边,自然的low[u]=min(low[u],dfn[v])。 另外注意输出格式,点要从小到大排列;边(u,v)保证u<v,以u从小到大排列,再以v从小到大排列。完整代码如下: [cpp] #include<iostream> #include<cstdio> #include<set> #include<algorithm> #include<vector> using namespace std; const int kMaxN = 20005; int n, m; typedef struct edge { int x, y; bool operator<(const edge& p)const//如果要加入到set中,需要重载< { return (this->x<p.x) || ((this->x == p.x) && (this->y<p.y)); } }; set<int> points; set<edge> edges; vector<vector<int>> graph; int visit[kMaxN]; int dfn[kMaxN]; int low[kMaxN]; int parent[kMaxN]; void dfs(int u) { //记录dfs遍历次序 static int counter = 0; //记录节点u的子树数 int children = 0; visit[u] = 1; //初始化dfn与low dfn[u] = low[u] = ++counter; for (int i = 0; i < graph[u].size();i++) { int v = graph[u][i]; //节点v未被访问,则(u,v)为树边 if (visit[v]==0) { children++; parent[v] = u; dfs(v); low[u] = min(low[u], low[v]); //case (1) if (parent[u] == 0 && children > 1) { points.insert(u); } //case (2) if (parent[u] != 0 && low[v] >= dfn[u]) { points.insert(u); } //bridge if (low[v] > dfn[u]) { edge e; e.x = u; e.y = v; if (u > v) { e.x = v; e.y = u; } edges.insert(e); } } //节点v已访问,则(u,v)为回边 else if (v != parent[u]) { low[u] = min(low[u], dfn[v]); } } } int main() { scanf("%d %d", &n, &m); graph.resize(n + 1); int u, v; while (m–) { scanf("%d %d", &u, &v); graph[u].push_back(v); graph[v].push_back(u); } dfs(1); if (points.size() == 0) printf("Null\n"); else { set<int>::iterator it = points.begin(); while (it != points.end()) { printf("%d ", *it); it++; } printf("\n"); } if (edges.size() > 0) { set<edge>::iterator it = edges.begin(); while (it != edges.end()) { printf("%d %d\n", (*it).x, (*it).y); it++; } } return 0; } [/cpp] 本代码提交AC,用时76MS,内存7MB。]]>

hihoCoder week 51-1-欧拉路·三

hihoCoder week 51-1-欧拉路·三 题目1 : 欧拉路·三 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho破解了一道又一道难题,终于来到了最后一关。只要打开眼前的宝箱就可以通关这个游戏了。 宝箱被一种奇怪的机关锁住: 这个机关是一个圆环,一共有2^N个区域,每个区域都可以改变颜色,在黑白两种颜色之间切换。 小Ho控制主角在周围探索了一下,果然又发现了一个纸片: 机关黑色的部分表示为1,白色的部分表示为0,逆时针连续N个区域表示一个二进制数。打开机关的条件是合理调整圆环黑白两种颜色的分布,使得机关能够表示0~2^N-1所有的数字。 我尝试了很多次,终究没有办法打开,只得在此写下机关破解之法。 ——By 无名的冒险者 小Ho:这什么意思啊? 小Hi:我给你举个例子,假如N=3,我们通过顺时针转动,可以使得正下方的3个区域表示为: 因为黑色表示为1,白色表示为0。则上面三个状态分别对应了二进制(001),(010),(101) 每转动一个区域,可以得到一个新的数字。一共可以转动2^N次,也就是2^N个数字。我们要调整黑白区域的位置,使得这2^N个数字恰好是0~2^N-1 小Ho:我懂了。若N=2,则将环上的黑白色块调整为”黑黑白白”,对应了”1100″。依次是”11″,”10″,”00″,”01″四个数字,正好是0~3。那么这个”黑黑白白”就可以打开机关了咯? 小Hi:我想应该是的。 小Ho:好像不是很难的样子,我来试试! 提示:有向图欧拉回路 输入 第1行:1个正整数,N。1≤N≤15 输出 第1行:1个长度为2^N的01串,表示一种符合要求的分布方案 样例输入 3 样例输出 00010111


本题是欧拉路的判定、无向图欧拉路的求解的更进一步,有向图欧拉路的求解。 在有向图中找欧拉路的算法和无向图中相同,还是Fleury算法,只不过这一题需要自己构造有向图。构造的方法详见题目提示,我一开始根据
对于任意两个点,如果点i,点j满足点i的后n-2个数字和点j的前n-2个数字相同,则我们连接有向边(i,j)。
这句话来构造的,略显麻烦。因为
而我们构造的图,由构造方法可以知道对于任意一个点,其入度一定为2,出度一定为2。所以它必定存在欧拉回路。
所以对于每一个点,它有且只有两个出度,我们只需要求出这两个出度即可,不需要0~n-1一个一个点判断。用二进制表示,对于一个特定的点$$x=a_1a_2…a_n$$,假设x出边指向的点为y和z,则y,z的前n-1位和x的后n-1位是相同的,也即$$y=a_2a_3…a_{n-1}0, z=a_2a_3…a_{n-1}1$$。用公式表示就是y=(x<<1)&(111…1),x左移一位,与上n个1,z=y+1。 把有向图构造好之后,按部就班用Fleury算法求解,再把结果倒序输出,输出的时候只要输出每个点的最后一位就行了。 完整代码如下 [cpp] #include<iostream> #include<cstdio> #include<cstring> using namespace std; const int kMaxN = 16; int edge[1 << kMaxN][2]; int path[1 << kMaxN]; int path_size = 0; int n; void MakeGraph() { memset(edge, -1, sizeof(edge)); int nn = 1 << (n – 1); for (int i = 0; i < nn; i++) { edge[i][0] = (i << 1)&(nn – 1); edge[i][1] = edge[i][0] + 1; } } void DFS(int p) { for (int i = 0; i < 2; i++) { int q = edge[p][i]; if (q != -1) { edge[p][i] = -1; DFS(q); } } path[path_size++] = p; } int main() { scanf("%d", &n); if (n == 1) { printf("01"); return 0; } else if (n == 2) { printf("1100"); return 0; } MakeGraph(); DFS(0); while (–path_size) printf("%d", path[path_size] & 1); return 0; } [/cpp] 本代码提交AC,用时8MS,内存1MB。]]>

hihoCoder 1121-二分图一•二分图判定

hihoCoder 1121-二分图一•二分图判定 #1121 : 二分图一•二分图判定 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 大家好,我是小Hi和小Ho的小伙伴Nettle,从这个星期开始由我来完成我们的Weekly。新年回家,又到了一年一度大龄剩男剩女的相亲时间。Nettle去姑姑家玩的时候看到了一张姑姑写的相亲情况表,上面都是姑姑介绍相亲的剩男剩女们。每行有2个名字,表示这两个人有一场相亲。由于姑姑年龄比较大了记性不是太好,加上相亲的人很多,所以姑姑一时也想不起来其中有些人的性别。因此她拜托我检查一下相亲表里面有没有错误的记录,即是否把两个同性安排了相亲。OK,让我们愉快的暴力搜索吧!才怪咧。对于拿到的相亲情况表,我们不妨将其转化成一个图。将每一个人作为一个点(编号1..N),若两个人之间有一场相亲,则在对应的点之间连接一条无向边。(如下图) 因为相亲总是在男女之间进行的,所以每一条边的两边对应的人总是不同性别。假设表示男性的节点染成白色,女性的节点染色黑色。对于得到的无向图来说,即每一条边的两端一定是一白一黑。如果存在一条边两端同为白色或者黑色,则表示这一条边所表示的记录有误。由于我们并不知道每个人的性别,我们的问题就转化为判定是否存在一个合理的染色方案,使得我们所建立的无向图满足每一条边两端的顶点颜色都不相同。那么,我们不妨将所有的点初始为未染色的状态。随机选择一个点,将其染成白色。再以它为起点,将所有相邻的点染成黑色。再以这些黑色的点为起点,将所有与其相邻未染色的点染成白色。不断重复直到整个图都染色完成。(如下图) 在染色的过程中,我们应该怎样发现错误的记录呢?相信你一定发现了吧。对于一个已经染色的点,如果存在一个与它相邻的已染色点和它的颜色相同,那么就一定存在一条错误的记录。(如上图的4,5节点)到此我们就得到了整个图的算法:选取一个未染色的点u进行染色 遍历u的相邻节点v:若v未染色,则染色成与u不同的颜色,并对v重复第2步;若v已经染色,如果 u和v颜色相同,判定不可行退出遍历。 若所有节点均已染色,则判定可行。 接下来就动手写写吧! 输入 第1行:1个正整数T(1≤T≤10) 接下来T组数据,每组数据按照以下格式给出: 第1行:2个正整数N,M(1≤N≤10,000,1≤M≤40,000) 第2..M+1行:每行两个整数u,v表示u和v之间有一条边 输出 第1..T行:第i行表示第i组数据是否有误。如果是正确的数据输出”Correct”,否则输出”Wrong” 样例输入 2 5 5 1 2 1 3 3 4 5 2 1 5 5 5 1 2 1 3 3 4 5 2 3 5 样例输出 Wrong Correct


本题考察二分图的判定,最简单的染色问题。 提示使用暴力搜索,其实就是BFS广度优先搜索。有一点需要注意的是,无向图可能有多个连通分量,比如下图。 此图有两个连通分量,且两个分量都可以相亲成功,但如果只从第1点开始BFS,则不能遍历到第二个分量,导致失败。所以需要对每一个还未染色的点都开始BFS,如果都尝试过了还有点未染色,二分图判定失败;否则成功。 完整代码如下: [cpp] #include<iostream> #include<cstdio> #include<vector> #include<queue> using namespace std; int n, m; vector<int> colored; vector<vector<int>> path; bool BFS(int start) { colored[start] = 0; int num_colored = 1; queue<int> point; point.push(start); while (!point.empty()) { int current = point.front(); point.pop(); for (int i = 0; i < path[current].size(); i++) { if (colored[path[current][i]] == -1) { if (colored[current] == 0) colored[path[current][i]] = 1; else if (colored[current] == 1) colored[path[current][i]] = 0; num_colored++; point.push(path[current][i]); } else if (colored[current] == colored[path[current][i]]) return false; } } return true; } bool Check() { for (int i = 1; i <= n; i++) if (colored[i] == -1) if (!BFS(i)) return false; return true; } int main() { int t,u,v; scanf("%d", &t); while (t–) { scanf("%d %d", &n, &m); colored.clear(); colored.resize(n + 1, -1); path.clear(); path.resize(n + 1); for (int i = 0; i < m; i++) { scanf("%d %d", &u, &v); path[u].push_back(v); path[v].push_back(u); } if (Check()) printf("Correctn"); else printf("Wrongn"); } return 0; } [/cpp] 本代码提交AC,用时134MS,内存6MB。 ]]>

hihoCoder week 50-1-欧拉路·二

hihoCoder week 50-1-欧拉路·二 题目1 : 欧拉路·二 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 在上一回中小Hi和小Ho控制着主角收集了分散在各个木桥上的道具,这些道具其实是一块一块骨牌。 主角继续往前走,面前出现了一座石桥,石桥的尽头有一道火焰墙,似乎无法通过。 小Hi注意到在桥头有一张小纸片,于是控制主角捡起了这张纸片,只见上面写着: 将M块骨牌首尾相连放置于石桥的凹糟中,即可关闭火焰墙。切记骨牌需要数字相同才能连接。 ——By 无名的冒险者 小Hi和小Ho打开了主角的道具栏,发现主角恰好拥有M快骨牌。 小Ho:也就是说要把所有骨牌都放在凹槽中才能关闭火焰墙,数字相同是什么意思? 小Hi:你看,每一块骨牌两端各有一个数字,大概是只有当数字相同时才可以相连放置,比如: 小Ho:原来如此,那么我们先看看能不能把所有的骨牌连接起来吧。 提示:Fleury算法求欧拉路径 输入 第1行:2个正整数,N,M。分别表示骨牌上出现的最大数字和骨牌数量。1≤N≤1,000,1≤M≤5,000 第2..M+1行:每行2个整数,u,v。第i+1行表示第i块骨牌两端的数字(u,v),1≤u,v≤N 输出 第1行:m+1个数字,表示骨牌首尾相连后的数字 比如骨牌连接的状态为(1,5)(5,3)(3,2)(2,4)(4,3),则输出”1 5 3 2 4 3″ 你可以输出任意一组合法的解。 样例输入 5 5 3 5 3 2 4 2 3 4 5 1 样例输出 1 5 3 4 2 3


上一周学习了怎样判断欧拉通路,这周要给出一条具体的欧拉通路。 欧拉通路的求解使用Fleury算法,其伪代码如下: [cpp] DFS(u): While (u存在未被删除的边e(u,v)) 删除边e(u,v) DFS(v) End PathSize ← PathSize + 1 Path[ PathSize ] ← u [/cpp] 非常简洁漂亮,本题完整代码如下: [cpp] #include<iostream> #include<cstdio> using namespace std; const int kMaxN = 1005,kMaxM=5005; int n, m,path_size=0; int path[kMaxM];//是kMaxM而非kMaxN int edge[kMaxN][kMaxN]; void dfs(int p) { for (int i = 1; i <= n; i++) { if (edge[p][i]>0) { edge[p][i]–; edge[i][p]–; dfs(i); } } path[path_size++] = p; } int main() { //freopen("input.txt", "r", stdin); scanf("%d %d", &n, &m); int u, v; for (int i = 0; i < m; i++) { scanf("%d %d", &u, &v); edge[u][v]++;//防止有重边 edge[v][u]++; } dfs(1); for (int i = 0; i < path_size; i++) printf("%d ", path[i]); return 0; } [/cpp] 本代码提交AC,用时15MS,内存3MB。需要提醒的是,path的实际大小是m+1,而非n;另外为了防止有重边出现,edge用于记录边的数量,而非是否有边存在。 AC之后我还尝试了先找出欧拉通路的起点,再从起点开始dfs的方法,这种方法并没有更快,因为不论是从哪个点开始dfs,每个点的for循环都要完整执行,所以时间并不会减少,反而会由于寻找起点而增加时间。]]>