概述
【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代码】
#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代码】
#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代码】
#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代码】
#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代码】
#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 Contest)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复