【ACM-ICPC】NEERC-2017(Clone Contest)

简单记录一下比赛中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 h\times 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)/2\rfloor \times \lfloor(w+1)/2\rfloor ⌊(h+1)/2×⌊(w+1)/2;② 不一定全部取到,实际上取值范围为集合 { 0 , 1 , . . . , ⌊ ( h + 1 ) / 2 ⌋ } \{0,1,...,\lfloor(h+1)/2\rfloor\} { 0,1,...,⌊(h+1)/2⌋}中的元素与集合 { 0 , 1 , . . . , ⌊ ( w + 1 ) / 2 ⌋ } \{0,1,...,\lfloor(w+1)/2\rfloor\} { 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 i1次;对列同理。

【时空复杂度】 O ( h ∗ w ) O ( h ∗ w ) O(h*w) \quad O(h*w) O(hw)O(hw)

【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 2n1种方案。

这样,我们的主要任务就是识别这样的变量 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 2\times 10^8 219×1922×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+k22k)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("%s\n", 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

笔者水平有限,代码也只是能过,算法和写法都不一定最优,请见谅。也欢迎大佬们分享自己的思路。

猜你喜欢

转载自blog.csdn.net/weixin_42430021/article/details/127144698
今日推荐