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 }
View Code

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 }
View Code

 

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 }
View Code

 

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 }
View Code

 

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 }
View Code

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 }
View Code

 

WA:一直在想暴力搜索,没有手推树的性质。

 

posted @ 2018-02-23 00:48  大四开始ACM  阅读(612)  评论(0编辑  收藏  举报