2001-2002 ACM Northeastern European Regional Programming Contest
很难。有些题是想到算法,但是实现很差,对照别人的代码才写出来。跟多的是看别人代码才明白的做法。不过都是质量很高的题,值得学习。
A. Team Them Up!
题意:有n个人,n不超过100,告诉你每个人都认识谁(不是对称的),让你把所有的人分成两组,在一个组内,每一个人都必须认识所有组内其他的人。问你是否有解,有解的话,让你输出一组使得两组人数差最小的方案。
观察:题目本质是给一个无向图,要求你把图划分成顶点数差值最小的两个clique。无向图是因为,对于a认识b,但b不认识a的情况,我们并不用考虑a到b的边。所以只保留输入中双向都认识的边,得到一个无向图。但是这个好像不是很好做,我们想到要分成两组,应该是和二分图有关。如果我们建立一下原图的补图,即把人看作点,如果两个人不是互相认识的话,就连一条边。题目要求你把这个图二分染色,且黑白点个数差最小。这就很好做了。先把图黑白染色,不能黑白染色(不是二分图),那就无解。对一个连通分量染色时,我们记录下两种颜色各自对应的点数,然后背包一下就好了。注意,背包的时候可以只计算选其中一组可以选出的人数,因为另一组的人数即mid-当前组的人数。
code:
1 /* 2 by skydog 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <vector> 7 #include <utility> 8 #include <algorithm> 9 #include <cmath> 10 #include <cstring> 11 #include <map> 12 #include <set> 13 #include <stack> 14 #include <queue> 15 #include <deque> 16 #include <cassert> 17 #include <list> 18 using namespace std; 19 typedef long long ll; 20 typedef pair<int, int> ii; 21 typedef pair<ll, ll> l4; 22 23 #define mp make_pair 24 #define pb push_back 25 #define db(x) cerr << #x << " = " << x << endl 26 27 28 const int maxn = 1e2+1; 29 int e[maxn][maxn]; 30 vector<int> g[maxn]; 31 vector<int> res[maxn][2]; 32 int tot, n, c[maxn]; 33 bool dfs(int cur, int color) 34 { 35 if (c[cur] != -1) 36 return c[cur] == color; 37 c[cur] = color; 38 res[tot][color].pb(cur); 39 for (auto nxt : g[cur]) 40 if (!dfs(nxt, 1-color)) 41 return false; 42 return true; 43 } 44 int pre[maxn][maxn]; 45 int main() 46 { 47 scanf("%d", &n); 48 for (int i = 1; i <= n; ++i) 49 { 50 int x; 51 while (~scanf("%d", &x) && x) 52 ++e[i][x]; 53 } 54 for (int i = 1; i <= n; ++i) 55 for (int j = i+1; j <= n; ++j) 56 if (!e[i][j] || !e[j][i]) 57 g[i].pb(j), g[j].pb(i); 58 memset(c, -1, sizeof(c)); 59 tot = 0; 60 for (int i = 1; i <= n; ++i) 61 if (c[i] == -1) 62 { 63 ++tot; 64 if (!dfs(i, 0)) 65 { 66 puts("No solution"); 67 return 0; 68 } 69 } 70 memset(pre, -1, sizeof(pre)); 71 pre[0][0] = true; 72 for (int i = 1; i <= tot; ++i) 73 { 74 for (int t = 0; t < 2; ++t) 75 for (int j = 0; j <= n; ++j) 76 if (pre[i-1][j] != -1 && pre[i][j+res[i][t].size()] == -1) 77 { 78 pre[i][j+res[i][t].size()] = t; 79 } 80 } 81 for (int tar = n/2; tar >= 0; --tar) 82 if (pre[tot][tar] != -1) 83 { 84 vector<int> a[2]; 85 for (int i = tot; i >= 1; --i) 86 { 87 int tmp = pre[i][tar]; 88 for (int t = 0; t < 2; ++t) 89 a[t].insert(a[t].end(), res[i][t^tmp].begin(), res[i][t^tmp].end()); 90 tar -= res[i][tmp].size(); 91 } 92 assert(tar == 0); 93 for (int i = 0; i < 2; ++i) 94 { 95 printf("%d", a[i].size()); 96 for (auto e : a[i]) 97 printf(" %d", e); 98 puts(""); 99 } 100 return 0; 101 } 102 assert(false); 103 }
WA:没有看清楚输入,以为认识关系是对称的。想麻烦了,背包了差值,但其实只需背包一组的大小即可。每个联通分量二分染色时,可以只用0,1和连通分量的id区分。
B. Brackets Sequence
题意:定义了regular sequence:(1)empty sequence是regular。(2)如果S是regular,[S], (S) 都是regular。(3)如果A,B是regular,AB也是regular。下面给你一个长度不超过100,且只由()[]四种自符组成的字符串,让你添加最少的字符,使得它变成regular。输出最终regular的字符串。
观察:数据很小,可以dp一下。dp[l][r]表示把s[l,...,r]变regular所需要的添加字符的最小数量(代价)。初始如果l > r,dp(l, r) = 0,表示空串不需要添加字符。有四种转移方式吧:
(1)如果s[l] 是open的括号,可以在s[r]的后面加一个对应的closed括号, 代价是 1+dp(l+1, r)。
(2)如果s[r] 是closed的括号,可以在s[l]的前面加一个对应的open括号,代价是 dp(l, r-1) + 1。
(3)如果s[l] 和 s[r] 可以match,直接处理下一层,代价是dp(l+1, r-1)。
(4)找中间一个分割点i (l <= i < r),分别处理s[l,...,i] 和 s[i+1,...,r],代价是dp(l, i) + dp(i+1, r)。
最后输出方案的时候,比较一些dp值就好了。
code:
1 /* 2 by skydog 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <vector> 7 #include <utility> 8 #include <algorithm> 9 #include <cmath> 10 #include <cstring> 11 #include <map> 12 #include <set> 13 #include <stack> 14 #include <queue> 15 #include <deque> 16 #include <cassert> 17 #include <list> 18 using namespace std; 19 typedef long long ll; 20 typedef pair<int, int> ii; 21 typedef pair<ll, ll> l4; 22 23 #define mp make_pair 24 #define pb push_back 25 #define db(x) cerr << #x << " = " << x << endl 26 27 28 const int maxn = 1e2+10; 29 char s[maxn]; 30 char match[200]; 31 const string alp = "()[]"; 32 inline bool left(char c) 33 { 34 return c < match[c]; 35 } 36 int d[maxn][maxn]; 37 inline void Min(int &a, int b) 38 { 39 if (a > b) a = b; 40 } 41 int dp(int l, int r) 42 { 43 if (l > r) return 0; 44 int &ret = d[l][r]; 45 if (ret == -1) 46 { 47 ret = 2e9; 48 if (left(s[l])) 49 Min(ret, 1+dp(l+1, r)); 50 if (!left(s[r])) 51 Min(ret, 1+dp(l, r-1)); 52 if (left(s[l]) && match[s[l]] == s[r]) 53 Min(ret, dp(l+1, r-1)); 54 for (int i = l; i < r; ++i) 55 Min(ret, dp(l, i)+dp(i+1, r)); 56 } 57 return ret; 58 } 59 void print(int l, int r) 60 { 61 if (l > r) 62 return; 63 int x = dp(l, r); 64 if (left(s[l])) 65 { 66 if (x == 1+dp(l+1, r)) 67 { 68 putchar(s[l]); 69 print(l+1, r); 70 putchar(match[s[l]]); 71 return; 72 } 73 else if (match[s[l]] == s[r] && x == dp(l+1, r-1)) 74 { 75 putchar(s[l]); 76 print(l+1, r-1); 77 putchar(match[s[l]]); 78 return; 79 } 80 } 81 if (!left(s[r]) && x == 1+dp(l, r-1)) 82 { 83 putchar(match[s[r]]); 84 print(l, r-1); 85 putchar(s[r]); 86 return; 87 } 88 for (int i = l; i < r; ++i) 89 if (x == dp(l, i) + dp(i+1, r)) 90 { 91 print(l, i); 92 print(i+1, r); 93 return; 94 } 95 assert(false); 96 } 97 int main() 98 { 99 for (int i = 0; i < alp.size(); ++i) 100 match[alp[i]] = alp[i^1]; 101 scanf("%s", s); 102 memset(d, -1, sizeof(d)); 103 print(0, strlen(s)-1); 104 }
C. Cable Master
题意:给你不超过10000个木根,让你切出k (<=10000)个等长的片段,问一个片段的长度最大是多少。输入和输出都是严格两位小数,输入的长度不超过100000.00。
观察:比较明显就是一个二分答案,需要注意的是输入输出都是严格2位小数,这时候可以把输入乘100,当作整数二分来做,不会有精度的烦恼。
code:
1 /* 2 by skydog 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <vector> 7 #include <utility> 8 #include <algorithm> 9 #include <cmath> 10 #include <cstring> 11 #include <map> 12 #include <set> 13 #include <stack> 14 #include <queue> 15 #include <deque> 16 #include <cassert> 17 #include <list> 18 using namespace std; 19 typedef long long ll; 20 typedef pair<int, int> ii; 21 typedef pair<ll, ll> l4; 22 23 #define mp make_pair 24 #define pb push_back 25 #define db(x) cerr << #x << " = " << x << endl 26 27 28 int read() 29 { 30 int ret = 0; 31 char ch = getchar(); 32 while (!isdigit(ch)) 33 ch = getchar(); 34 while (isdigit(ch)) 35 ret = 10*ret + ch-'0', ch = getchar(); 36 assert(ch == '.'); 37 ch = getchar(); 38 while (isdigit(ch)) 39 ret = 10*ret + ch-'0', ch = getchar(); 40 return ret; 41 } 42 int n, k; 43 const int maxn = 1e4; 44 int len[maxn]; 45 bool valid(int mid) 46 { 47 int cnt = 0; 48 for (int i = 0; i < n; ++i) 49 cnt += len[i]/mid; 50 return cnt >= k; 51 } 52 int main() 53 { 54 scanf("%d %d", &n, &k); 55 for (int i = 0; i < n; ++i) 56 len[i] = read(); 57 int l = 1, r = 1e7, mid, ans = 0; 58 while (l <= r) 59 { 60 mid = (l+r)>>1; 61 if (valid(mid)) 62 { 63 ans = mid; 64 l = mid+1; 65 } 66 else 67 { 68 r = mid-1; 69 } 70 } 71 printf("%d.", ans/100); 72 ans %= 100; 73 if (ans < 10) 74 printf("0"); 75 printf("%d\n", ans); 76 }
D. Wall
题意:给你一个不超过1000个点的simple polygon,让你在周围建立围栏,且围栏到polygon上任意点的位置都不小于L。问你围栏长度的最小值。
观察:可以先考虑一个极端情况。如果L等于0,那么怎样围最优呢?肯定是凸包,原因就是两点间直线段最短。当L不等于0的时候也是类似,现求出凸包,考虑一个半径为L的圆,圆心在凸包上移动,它扫过的图形。这个图形的外围就是我们要找的围栏,长度也很好计算,因为这个凸包是凸的,外围圆线段的拼接在一起是一个整圆,长度是2*pi*L,剩下直线段的部分,长度于凸包相同。答案就是凸包周长加一个半径为L的圆的周长。为什么L不等于0的时候还是这样最优。可以考虑图形的顶点中,在凸包上的顶点,和以些点为圆心,半径为L的圆。最终答案中一定会包含这些圆的一部分。那么用围栏连接相邻的圆,考虑相邻两个圆外侧的公切线,分别与两个圆且在A点和B点,那么从A到B,肯定是直线段最短。
code:
1 /* 2 by skydog 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <vector> 7 #include <utility> 8 #include <algorithm> 9 #include <cmath> 10 #include <cstring> 11 #include <map> 12 #include <set> 13 #include <stack> 14 #include <queue> 15 #include <deque> 16 #include <cassert> 17 #include <list> 18 using namespace std; 19 typedef long long ll; 20 typedef pair<int, int> ii; 21 typedef pair<ll, ll> l4; 22 23 #define mp make_pair 24 #define pb push_back 25 #define db(x) cerr << #x << " = " << x << endl 26 27 28 const int maxn = 1e3+10; 29 int n, l; 30 struct Point 31 { 32 int x, y; 33 inline void read() 34 { 35 scanf("%d %d", &x, &y); 36 } 37 Point operator-(const Point &r) const 38 { 39 Point p; 40 p.x = x-r.x; 41 p.y = y-r.y; 42 return p; 43 } 44 double len() const 45 { 46 return sqrt(x*x+y*y); 47 } 48 } p[maxn], ch[maxn]; 49 bool cmp(const Point &a, const Point &b) 50 { 51 if (a.x != b.x) 52 return a.x < b.x; 53 return a.y < b.y; 54 } 55 int cross(const Point &lhs, const Point &rhs) 56 { 57 return lhs.x*rhs.y-lhs.y*rhs.x; 58 } 59 int convex_hull(Point *v, int n, Point *z) 60 { 61 sort(v, v+n, cmp); 62 int m = 0; 63 for (int i = 0; i < n; ++i) 64 { 65 while (m > 1 && cross(z[m-1]-z[m-2], v[i]-z[m-2]) <= 0) --m; 66 z[m++] = v[i]; 67 } 68 int k = m; 69 for (int i = n-2; i >= 0; --i) 70 { 71 while (m > k && cross(z[m-1]- z[m-2], v[i]-z[m-2]) <= 0) --m; 72 z[m++] = v[i]; 73 } 74 if (n > 1) 75 --m; 76 return m; 77 } 78 double dis(const Point &lhs, const Point &rhs) 79 { 80 return (lhs-rhs).len(); 81 } 82 int main() 83 { 84 scanf("%d %d", &n, &l); 85 for (int i = 0; i < n; ++i) 86 p[i].read(); 87 int m = convex_hull(p, n, ch); 88 double ans = 2*acos(-1.0)*l; 89 for (int i = 0; i < m; ++i) 90 ans += dis(ch[i], ch[(i+1)%m]); 91 printf("%lld\n", (ll)round(ans)); 92 }
E. Chemical Reactions
题意:给你一个化学方程式的定义式(正则表达式?),再给你一些方程式,让你做一些判断,判断某两个方程式中的元素数量是否相同。每个方程式长度不超过100,保证一个方程式中,一种元素加权后的出现次数不超过10000。
观察:其实就是写一个parser。因为长度和数量都有上限,我们就不用太考虑常数,直接上map<string, int>记录各个元素的数量。至于怎么parse,直接按照它的定义一步一步写就好了,竟然异常的好写,难以置信,感觉豁然开朗。
code:
1 /* 2 by skydog 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <vector> 7 #include <utility> 8 #include <algorithm> 9 #include <cmath> 10 #include <cstring> 11 #include <map> 12 #include <set> 13 #include <stack> 14 #include <queue> 15 #include <deque> 16 #include <cassert> 17 #include <list> 18 using namespace std; 19 typedef long long ll; 20 typedef pair<int, int> ii; 21 typedef pair<ll, ll> l4; 22 23 #define mp make_pair 24 #define pb push_back 25 #define db(x) cerr << #x << " = " << x << endl 26 27 const int maxn = 1e2+1; 28 int p; 29 const int N = 11; 30 char *s, str[N][maxn]; 31 map<string, int> v[N]; 32 int len; 33 inline int get_num() 34 { 35 if (!isdigit(s[p])) 36 return 1; 37 int ret = 0; 38 while (isdigit(s[p])) 39 ret = 10*ret + s[p++]-'0'; 40 return ret; 41 } 42 inline string get_chem() 43 { 44 string ret = ""; 45 if (!isupper(s[p])) 46 return ret; 47 ret += s[p++]; 48 while (islower(s[p])) 49 ret += s[p++]; 50 return ret; 51 } 52 inline map<string, int> get_elem(); 53 void merge(map<string, int> &a, const map<string, int> &b) 54 { 55 for (const auto &e : b) 56 a[e.first] += e.second; 57 } 58 void mul(map<string, int> &a, int b) 59 { 60 for (auto &e : a) 61 e.second *= b; 62 } 63 inline map<string, int> get_seq() 64 { 65 map<string, int> ret, tmp; 66 for (;;) 67 { 68 tmp = get_elem(); 69 if (tmp.empty()) 70 break; 71 int cnt = get_num(); 72 mul(tmp, cnt); 73 merge(ret, tmp); 74 } 75 return ret; 76 } 77 inline map<string, int> get_elem() 78 { 79 map<string, int> ret; 80 if (s[p] == '(') 81 { 82 ++p; 83 ret = get_seq(); 84 //assert(s[p] == ')'); 85 ++p; 86 } 87 else 88 { 89 string tmp = get_chem(); 90 if (!tmp.empty()) 91 ret[tmp] = 1; 92 } 93 return ret; 94 } 95 map<string, int> get_for() 96 { 97 map<string, int> ret, tmp; 98 for (;;) 99 { 100 int cnt = get_num(); 101 tmp = get_seq(); 102 if (tmp.empty()) 103 break; 104 mul(tmp, cnt); 105 merge(ret, tmp); 106 if (s[p] == '+') 107 ++p; 108 else 109 { 110 assert(s[p] == 0); 111 break; 112 } 113 } 114 return ret; 115 } 116 bool same(const map<string, int> &lhs, const map<string, int> &rhs) 117 { 118 if (lhs.size() != rhs.size()) 119 return false; 120 auto lit = lhs.begin(), rit = rhs.begin(); 121 while (lit != lhs.end()) 122 { 123 if (*lit != *rit) 124 return false; 125 ++lit, ++rit; 126 } 127 return true; 128 } 129 int main() 130 { 131 scanf("%s", str[0]); 132 int n; 133 scanf("%d", &n); 134 for (int i = 1; i <= n; ++i) 135 scanf("%s", str[i]); 136 for (int i = 0; i <= n; ++i) 137 { 138 p = 0; 139 s = str[i]; 140 v[i] = get_for(); 141 } 142 for (int i = 1; i <= n; ++i) 143 { 144 printf("%s%c=%s\n", str[0], same(v[0], v[i])?'=':'!', str[i]); 145 } 146 }
WA:没有看清楚数字的定义
F. Statistical Trouble
题意:
观察:
code:
G. Library
题意:
观察:
code:
H. Pairs of Integers
题意:给你一个整数n, 10 <= n <= 1e9。让你找出所有的自然数A, B,满足A无前导零,B可以有前导零,A的长度 = B的长度+1, A+B = n。
观察:
可以列一下A和B的形式,如果把A和B看成string A_str 和 B_str,那么A和B可以写成 B_str = prefix + suffix, A_str = prefix + a_single_digit + suffix,落实到数字,就是 B = prefix*10^(length of suffix) + suffix, A = prefix * 10^(1 + length of suffix) + a_single_digit * 10^(length of suffix) + suffix,简记为 B = p*10^slen + s, A = p*10^(slen+1)+d*10^slen+s。注意,prefix和suffix都可以是空的,但是d一定是一个数位。
由A + B = n,我们得到,(11*p+d)*10^slen+2*s = n。我们可以考虑枚举slen。因为slen是s的长度,那么2*s的长度至多是slen+1,而且当2*s的长度是slen+1时,它最高的数位一定是1。我们实现一个函数,叫做solve(X, Y),表示2*s = X,11*p+d = Y时,找到合适的解。那么我们枚举slen,每次调用solve(n%10^slen, n/10^slen) 和 solve(n%10^slen + 10^slen,n/10^lsen - 1)。
对于solve(X, Y), X一定要是偶数,否则无解,那么2*s = X, s = X/2。对于11*p+d = Y,因为d是一个数字,我们只需要枚举所有的可能就好了。
记得最后把答案去一下重。这个题对拍很好写,所以找出自己算法的错误也比较容易。
code:
1 /* 2 by skydog 3 */ 4 #include <iostream> 5 #include <cstdio> 6 #include <vector> 7 #include <utility> 8 #include <algorithm> 9 #include <cmath> 10 #include <cstring> 11 #include <map> 12 #include <set> 13 #include <stack> 14 #include <queue> 15 #include <deque> 16 #include <cassert> 17 #include <list> 18 using namespace std; 19 typedef long long ll; 20 typedef pair<int, int> ii; 21 typedef pair<ll, ll> l4; 22 23 #define mp make_pair 24 #define pb push_back 25 #define db(x) cerr << #x << " = " << x << endl 26 27 const int maxn = 1e5; 28 int length[maxn]; 29 inline int len(int x) 30 { 31 if (!x) return 1; 32 return x < maxn ? length[x] : 5 + length[x/maxn]; 33 } 34 void print(int x, int length) 35 { 36 int extra = length-len(x); 37 for (int i = 0; i < extra; ++i) 38 putchar('0'); 39 printf("%d", x); 40 } 41 vector<int> ans; 42 int n; 43 void solve(int a, int bc, int base) 44 { 45 if (a%2 || bc == 0) 46 return; 47 a /= 2; 48 for (int b = 0; b < 10 && b <= bc; ++b) 49 { 50 int cc = bc-b; 51 if (cc%11) 52 continue; 53 int c = cc/11; 54 ans.pb(a+base*c); 55 } 56 } 57 bool check(int a, int b) 58 { 59 bool done = false; 60 while (a) 61 { 62 if (a%10 == b%10) b/=10; 63 else if (done) 64 return false; 65 else 66 done = true; 67 a /= 10; 68 } 69 return done && b == 0; 70 } 71 bool check(int n) 72 { 73 74 for (int i = 0; i < n/2; ++i) 75 ; 76 return true; 77 } 78 void solve(int n) 79 { 80 ans.clear(); 81 82 } 83 int main() 84 { 85 for (int i = 1; i < maxn; ++i) length[i] = length[i/10]+1; 86 scanf("%d", &n); 87 for (ll base = 1; base <= n; base *= 10) 88 { 89 solve(n%base, n/base, base); 90 solve(n%base+base, (n-base)/base, base); 91 } 92 sort(ans.begin(), ans.end(), greater<int>()); 93 ans.resize(unique(ans.begin(), ans.end())-ans.begin()); 94 printf("%d\n", ans.size()); 95 for (auto e : ans) 96 { 97 printf("%d + ", n-e); 98 print(e, len(n-e)-1); 99 printf(" = %d\n", n); 100 } 101 }
WA:一直在想暴力搜索,没有手推树的性质。