【ACM-ICPC】NEERC-2017(Clone Contest)
- A. Auxiliary Project (思维+贪心)
- K. Kotlin Island (找规律+构造)
- B. Boolean Satisfiability (逻辑或的性质)
- C. Consonant Fencity (下标映射+二进制枚举+构造)
- I. Intelligence in Perpendicularia(学霸题)
简单记录一下比赛中AC的题目和思路,有空补一补没做的题。
做题顺序:A-K-B-C-I (只过了5题,不过对于我这蒟蒻来说已经不错了)
A. Auxiliary Project (思维+贪心)
忘记重定向WA了两发,哭哭qAq
【题目大意】给你充分多的LED单数字显示器,每个可以作为组成数字的一段,现要你点亮恰好n段,每段都必须是一个合法数字的组成部分(0-9),在所形成数字都合法的情况下,使得所有数字和最大,求该最大值。
【思路】以<需要的段数,组成的数字>作为二元组,这样依次可得: ( 6 , 0 ) (6,0) (6,0), ( 2 , 1 ) (2,1) (2,1), ( 5 , 2 ) (5,2) (5,2), ( 5 , 3 ) (5,3) (5,3), ( 4 , 4 ) (4,4) (4,4), ( 5 , 5 ) (5,5) (5,5), ( 6 , 6 ) (6,6) (6,6), ( 3 , 7 ) (3,7) (3,7), ( 7 , 8 ) (7,8) (7,8), ( 6 , 9 ) (6,9) (6,9),前者为成本,后者为收益,现在我们要在总成本恰好为n的前提下最大化收益(这与背包问题有所不同)。实际不难发现:①对于相同成本的,肯定会选择收益更大的;②数字0本身没有贡献,直接忽略;③对于成本可以通过线性组合得到的,如果线性组合出的总收益比当前成本的收益高,那就可以忽略当前单个的(比如两个 ( 3 , 7 ) (3,7) (3,7)可以合成一个 ( 6 , 14 ) (6,14) (6,14),这比单个的 ( 6 , 9 ) (6,9) (6,9)要大)。
做了以上观察后,可选集合可以缩小为: { ( 2 , 1 ) , ( 4 , 4 ) , ( 3 , 7 ) } {(2,1),(4,4),(3,7)} {(2,1),(4,4),(3,7)},由于 ( 3 , 7 ) (3,7) (3,7)性价比最高,所以能合成7就合7,这时候只需要根据n模3的结果分类讨论即可。
【时空复杂度】 O ( 1 ) O ( 1 ) O(1) quad O(1) O(1)O(1)
【AC代码】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29#include<bits/stdc++.h> using namespace std; #define REDIRECT int main(){ #ifdef REDIRECT freopen("auxiliary.in", "r", stdin); freopen("auxiliary.out", "w", stdout); #endif int n; cin >> n; switch(n%3){ // 3d + rem case 0: cout << n / 3 * 7 << endl; break; case 1: cout << (n/3-1)*7 + 4 << endl; break; case 2: // 2 5 8 if(n == 2){ cout << 1 << endl; }else{ cout << n/3*7 + 1 << endl; } break; } return 0; }
K. Kotlin Island (找规律+构造)
又忘记重定向了,WA了1发。
【题目大意】给你
h
×
w
htimes w
h×w的字符网格(起初全为点.
),你可以做任意次操作,每次操作可以选择一行或一列,将选择的整行或列上的单元格全部改为#
,问能否恰好使得.
的连通块数目为
n
n
n。如果能,构造出符合条件的该网格(任意一个),否则输出“Impossible”。
【思路】一开始没啥头绪,那就搞几个具体例子,画画图。思考这两个问题:① 对于给定大小的网格,能形成的连通块数组最大值是多少呢?② 如果该最大值为 m m m,那么连通块数目能否将 [ 0 , m ] [0,m] [0,m]全部取到呢?
画图后观察一下,不难得到这两个问题的答案,即① 对于 h × w h times w h×w的网格,最多的连通块数目为 ⌊ ( h + 1 ) / 2 ⌋ × ⌊ ( w + 1 ) / 2 ⌋ lfloor(h+1)/2rfloor times lfloor(w+1)/2rfloor ⌊(h+1)/2⌋×⌊(w+1)/2⌋;② 不一定全部取到,实际上取值范围为集合 { 0 , 1 , . . . , ⌊ ( h + 1 ) / 2 ⌋ } {0,1,...,lfloor(h+1)/2rfloor} {0,1,...,⌊(h+1)/2⌋}中的元素与集合 { 0 , 1 , . . . , ⌊ ( w + 1 ) / 2 ⌋ } {0,1,...,lfloor(w+1)/2rfloor} {0,1,...,⌊(w+1)/2⌋}中的元素的乘积结果所形成的集合。
这样思路就有了:首先看n是否在上述的集合中,如果不在的话就输出“Impossible”;否则就可以找到乘积为n的二元组
(
i
,
j
)
(i,j)
(i,j),即划分了
i
i
i行,每行
j
j
j列。这样构造答案时,我们只需要从第1行开始(起始是第0行),每隔一行将当前行全部变为#
,重复上述操作
i
−
1
i-1
i−1次;对列同理。
【时空复杂度】 O ( h ∗ w ) O ( h ∗ w ) O(h*w) quad O(h*w) O(h∗w)O(h∗w)
【AC代码】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47#include<bits/stdc++.h> using namespace std; #define REDIRECT int main(){ #ifdef REDIRECT freopen("kotlin.in", "r", stdin); freopen("kotlin.out", "w", stdout); #endif int h, w, n; cin >> h >> w >> n; int h1 = (h + 1) >> 1; int w1 = (w + 1) >> 1; auto check = [](int h, int w, int n){ for (int i=1; i<=h; ++i){ for (int j=1; j<=w; ++j){ if(i * j == n) return make_pair(i, j); } } return make_pair(0, 0); }; auto res = check(h1, w1, n); if(res.first == 0){ cout << "Impossible" << endl; }else{ int &row = res.first, &col = res.second; // cout << row << " " << col << endl; vector<string> a(h, string(w, '.')); for (int i=1, cnt=0; i<h && cnt<row-1; i+=2, cnt++){ a[i] = string(w, '#'); } for (int j=1, cnt=0; j<w && cnt<col-1; j+=2, cnt++){ for (int i=0; i<h; ++i){ a[i][j] = '#'; } } for (auto &s: a){ for (auto &ch: s){ cout << ch; } cout << endl; } } return 0; }
B. Boolean Satisfiability (逻辑或的性质)
【题目大意】给你一个逻辑表达式
F
F
F,由变量名(单字符,[a-zA-z],区分大小写),逻辑非~
和逻辑或|
构成,其中逻辑非只作用于变量名,每个变量的取值为真或假,现在你可以给每个不同的变量取具体的值,问你使得
F
F
F为真的取值方式有多少种。
【思路】不妨以逻辑或|
将逻辑表达式分为各个单元(clause),然后观察各样例,结合逻辑或的性质,不难发现:① 只要有一个单元的结果为真,那么其他单元的变量可以任意取值;② 对于
B
∣
¬
B
B |neg B
B∣¬B来说,不论
B
B
B取值如何,结果恒真。假如有
n
n
n个不同的变量,结合我们的发现,我们可以看②所描述的情况是否存在,如果存在的话,那么所有变量的取值都可以任意,这样就有
2
n
2^n
2n种方案;如果不存在,那么当且仅当所有单元结果为假时
F
F
F为假,只有一个方案能满足,因而反过来就有
2
n
−
1
2^n-1
2n−1种方案。
这样,我们的主要任务就是识别这样的变量 v a r var var,使得 v a r var var和 ¬ v a r neg var ¬var都存在于表达式中。
【时空复杂度】 O ( n ) O ( n ) O(n) quad O(n) O(n)O(n) n quad n n为逻辑表达式长度
【AC代码】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50#include<bits/stdc++.h> using namespace std; #define REDIRECT using LL = long long; using pcb = pair<char, bool>; int main(){ #ifdef REDIRECT freopen("boolean.in", "r", stdin); freopen("boolean.out", "w", stdout); #endif string s; cin >> s; auto Split = [](const string &s){ vector<pcb> ret; for (int i=0; i<(int)s.length(); ++i){ if(s[i] == '~'){ ret.emplace_back(make_pair(s[i+1], false)); i++; }else if(s[i] != '|'){ ret.emplace_back(make_pair(s[i], true)); } } return ret; }; LL res = 0; vector<pcb> ret = Split(s); map<char, pair<bool, bool> > mp; for (auto &p: ret){ if(p.second){ // pos mp[p.first].first = true; }else{ // neg mp[p.first].second = true; } } res = 1LL << (int)mp.size(); bool flag = false; for (auto &p: mp){ auto &rs = p.second; if(rs.first && rs.second){ flag = true; break; } } if(!flag) res--; cout << res << endl; return 0; }
C. Consonant Fencity (下标映射+二进制枚举+构造)
下标映射错了(b映射为了0,但其他元音字母也为0,应该将元音字母映射为-1的),因此WA了一发,找了好久才发现。最后通过时的时间开销接近1s,也算比较大了。
【题目大意】给你一个长度不超过 1 0 6 10^6 106的小写字符构成的串,你可以将其中某个字符 s [ i ] s[i] s[i]改为大写,但这样操作会使得所有字符值为 s [ i ] s[i] s[i]的字符都变为大写,现在定义了一个指标 c o n s o n a n t f e n c i t y consonant fencity consonant fencity,它表示字符串中的二元组 < s [ i ] , s [ i + 1 ] > <s[i],s[i+1]> <s[i],s[i+1]>的总数,该二元组中的字符均为辅音( a e i o u y w aeiouyw aeiouyw除外的字母),且大小写不同。现在要你最大化这个指标(你可以操作任意次),且输出该指标最大时的对应字符串。
【思路】
注意到小写字母只有26个,且题目的指标值只与辅音字母有关,本题的辅音恰好有19个,由于同一个字母的大小写是一致的,因此我们可以枚举辅音字母大小写的所有情况,共
2
19
=
524288
2^{19}=524288
219=524288种,对于每种情况,我们计算其指标值,这只取决于每个各不相同的辅音字母,以及字符串中每个出现该辅音字母的后继辅音字符大小写的情况,每种情况的计算最多允许
O
(
k
2
)
O(k^2)
O(k2),其中
k
=
19
k=19
k=19即不同辅音字母的数目。
这样极限情况的计算次数为 2 19 × 1 9 2 ≃ 2 × 1 0 8 2^{19}times 19^2 simeq 2times 10^8 219×192≃2×108,在3秒时限下勉强能过,因此本题的难点在于尽可能优化每一种枚举情况内部的循环,按照上面的思路,我们需要将26个字母中的辅音字母单独拿出来,按顺序进行重映射,这样内部的循环能从26降为19。这题笔者的写法比较低效,仅供参考(调试过程中随机生成了 1 0 6 10^6 106的字符串用于压力测试)。
【时空复杂度】 O ( n + k 2 ∗ 2 k ) O ( n ) O(n +k^2*2^k) quad O(n) O(n+k2∗2k)O(n) n quad n n为字符串长度, k k k为辅音字母个数,此题中 k = 19 k=19 k=19
【AC代码】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71#include<bits/stdc++.h> using namespace std; #define REDIRECT using LL = long long; using pcb = pair<char, bool>; const int maxn = 1000010; char s[maxn]; map<char, int> adj[19]; char alpha[19] = {'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'x', 'z'}; int pos[26] = {-1, 0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, 9, 10, -1, 11, 12, 13, 14, 15, -1, 16, -1, 17, -1, 18}; int main(){ #ifdef REDIRECT freopen("consonant.in", "r", stdin); freopen("consonant.out", "w", stdout); #endif // freopen("genC.out", "r", stdin); scanf("%s", s); int n = strlen(s); for (int i=0; i<n; ++i){ if(pos[s[i]-'a'] != -1){ if(i < n-1 && pos[s[i+1]-'a'] != -1){ adj[pos[s[i]-'a']][s[i+1]]++; //cout << s[i] << " -> " << s[i+1] << endl; } } } /* for (int i=0; i<19; ++i){ auto &mp = adj[i]; cout << alpha[i] << ": " << endl; for (auto& p: mp){ cout << p.first << "|= " << p.second << endl; } cout << "================" << endl; }*/ int maxn = 0, ans =0; for (int mask=1; mask<1<<19; ++mask){ int cur = 0; for (int i=0; i<19; ++i){ int mi = mask >> i & 1; auto &mp = adj[i]; for (auto& p: mp){ int mj = mask >> pos[p.first-'a'] & 1; cur += (mi ^ mj) * p.second; } } if(maxn < cur){ maxn = cur; ans = mask; } } // cout << maxn << endl; // construct for (int i=0; i<n; ++i){ int idx = pos[s[i]-'a']; if(idx != -1 && ans & 1 << idx){ s[i] -= 32; } } printf("%sn", s); return 0; }
I. Intelligence in Perpendicularia(学霸题)
这题我想了好久,一开始往扫描线那块想,但越想越复杂,但一看这题的通过数非常多,就总觉得不对劲。然后在某一时刻灵光一现,心里直呼——我**是个傻币。
【题目大意】在一个二维平面上,给你一个由n个结点组成的多边形,n个结点按照构成多边形边的顺序给出,且每一条边或与x轴垂直,或与y轴垂直,现在从多边形外的四个垂直方向看多边形,问你看不到的那部分总周长为多少。
【思路】只要知道以下三点就GAME OVER了:① 对于多边形,从四个方向看去,一定是一整个连续的线段,中间不可能存在空缺(如果存在,那就一定不是同一个多边形了); ② 对于多边形的任意一条边的任意子段而言,不可能同时从两个相对的方向看到它(如果能的话,那这部分的面积就是0,这是不可能的);③ 由前两个规律得知,从某个方向看去,如果这个视角下形成的连续线段长度为 l l l,那么就拿多边形总周长减去这一段长度,对于四个方向皆如此。
因此,只需要累计每条边的长度,同时维护水平和竖直方向的线段两端点,最后拿累计的总周长减去两线段长度之和的两倍(两倍是因为每条线段从两个方向所见均如此)即可。实际上,这题类似三视图,只不过是二维投射到一维数轴上。
【时空复杂度】 O ( n ) O ( 1 ) O(n) quad O(1) O(n)O(1) n quad n n为节点数目
【AC代码】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54#include<bits/stdc++.h> using namespace std; #define REDIRECT const int INF = 0x3f3f3f3f; using LL = long long; int main(){ #ifdef REDIRECT freopen("intel.in", "r", stdin); freopen("intel.out", "w", stdout); #endif int n; cin >> n; int px, py; int minx = INF, maxx = -INF, miny = INF, maxy = -INF; LL res = 0; int inx, iny; for (int i=0; i<n; ++i){ int x, y; cin >> x >> y; if(i > 0){ if(x == px){ // vert miny = min(miny, min(y, py)); maxy = max(maxy, max(y, py)); res += abs(y - py); } else{ // hori minx = min(minx, min(x, px)); maxx = max(maxx, max(x, px)); res += abs(x - px); } }else{ inx = x, iny = y; } px = x, py = y; } if(inx == px){ // vert miny = min(miny, min(iny, py)); maxy = max(maxy, max(iny, py)); res += abs(iny - py); } else{ // hori minx = min(minx, min(inx, px)); maxx = max(maxx, max(inx, px)); res += abs(inx - px); } res -= 1LL * 2 * (maxx - minx) + 2 * (maxy - miny); cout << res << endl; return 0; }
newline
笔者水平有限,代码也只是能过,算法和写法都不一定最优,请见谅。也欢迎大佬们分享自己的思路。
最后
以上就是苗条未来最近收集整理的关于【ACM-ICPC】NEERC-2017(Clone Contest)的全部内容,更多相关【ACM-ICPC】NEERC-2017(Clone内容请搜索靠谱客的其他文章。
发表评论 取消回复