Codeforces Round #786 题解

个人思路,仅作记录,可以参考,欢迎交流。

比赛地址:​​​​​​​传送门​​​​​​​


A. Number Transformation

传送门

【题意】给定两个[1,100]内的整数x和y,求正整数a,b,使x*b^a=y

【思路】枚举[1,100]找b并求对应的a

【代码】

void solve()
{
	int x, y;
	cin >> x >> y;
	if (y % x)
	{
		cout << "0 0\n";
		return;
	}
	else if (y == x)
	{
		cout << "1 1\n";
		return;
	}
	int a = 0, b = 0;
	for (int i = 2; i <= 100; ++i)
	{
		int num = y / x;
		if (num % i == 0)
		{
			b = i;
			while (num > 1)
			{
				a++;
				num /= b;
				if (num > 1 && num % i)
				{
					a = 0; 
					break;
				}
			}
		}
		if (num == 1)
		{
			break;
		}
	}
	cout << a << " " << b << '\n';
	return;
}

B. Dictionary

传送门

【题意】给定一个含两个不同小写字母的单词s,求它在「所有含两个不同小写字母的单词」中的字典序

【思路】字典中以同一字母开头的单词有25个。

答案=首字母比s小的单词数+与s同首字母但第二个字母比s小的单词数+1

【代码】

void solve()
{
	string s;
	cin >> s;
	cout << (s[0] - 'a') * 25 + (s[1] > s[0] ? s[1] - 'a' - 1 : s[1] - 'a') + 1 << '\n';
	return;
}

C. Infinite Replacement

传送门

【题意】给定一个含n个’a’的字符串s和一个字符串t,求任意将s中的’a’用t替换能产生多少种不同的字符串

【思路】先考虑t中含’a’的特殊情况:

  1. ①如果t==”a”,怎么替换都不变,答案为1;
  2. ②如果t含’a’且长度大于1,可以无限套娃替换,答案为∞;

再考虑一般情况:

  1. ③如果t不含’a’,s中每个’a’都有2种情况——替换或不替换,答案为2^n

【代码】

void solve()
{
	string s, t;
	cin >> s >> t;
	long long ans;
	if (t == "a")
	{
		ans = 1;
	}
	else if (t.find('a') != string::npos)
	{
		ans = -1;
	}
	else
	{
		ans = (1ll << s.length());
	}
	cout << ans << '\n';
	return;
}

D. A-B-C Sort

传送门

【题意】给定一个正整数数组A和一个包含两个操作的过程:

  1. 1. 不断取出A中最后一个数,放入空数组B的中间或中间数的两边
  2. 2. 不断取出B的中间数或中间两数的任一个,放入空数组C的末尾

求经过这一过程后C是否有可能为非降数组

【思路】题所说的过程即:每次从A末尾取出两个数,任意顺序分别放入两个栈中。A为空后,再每次分别取出两栈中等高度的两个数,任意顺序放入C末尾(如果A数组大小为奇数,则最后一个数单独特殊考虑)。经过分析,这个过程的作用其实就可以简化为:从A末尾开始算,每两个数之间可以调换顺序。显然,每次取出的两个数都小于等于上一次取出的两个数,最后才可能排序成功。

【代码】

int a[200005];

void solve()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; ++i)
	{
		cin >> a[i];
	}
	if (n <= 2)
	{
		cout << "YES\n";
		return;
	}
	for (int i = n - 2; i >= 1; i -= 2)
	{
		if (i > 1)
		{
			if (a[i] <= a[i + 2] && a[i - 1] <= a[i + 1] && a[i] <= a[i + 1] && a[i - 1] <= a[i + 2])
			{
				continue;
			}
			else
			{
				cout << "NO\n";
				return;
			}
		}
		else
		{
			if (a[i] <= a[i + 1] && a[i] <= a[i + 2])
			{
				continue;
			}
			else
			{
				cout << "NO\n";
				return;
			}
		}
	}
	cout << "YES\n";
	return;
}

E. Breaking the Wall

传送门

(第一次被Hack,居然是犯了个低级错,呜呜)

【题意】给定一个正整数组和一个操作:

  • 选定数组中一个数,使该数-2且与该数相邻的数-1

求最少几次操作可以使数组中出现两个或以上小于等于0的数

【思路】最朴素的想法是尽量挑两个软柿子捏。但是由于相邻的数也会-1这一特性,还要考虑一些被减到0的数原来不是最小的特殊情况。可以分三种情况讨论:

  1. 1. 这两个数相邻,则每次优先对两个数中较大的一个进行操作(总之两数都大于0时两数之和每次要减少3),直到成功
  2. 2. 这两个数间隔1,则可以先对两数之间的数进行操作,直到其中两数之一减为0后,再对剩下的一个数进行操作(总之两数之和每次要减少2,我就栽在这了)
  3. 3.这两个数间隔大于1,则分别对它们进行操作直到都减为0

把这三种情况都考虑一遍,就能得到最少操作数。

【代码】

int a[200005];

int adj(int a, int b)
{
	if (a < b)
	{
		swap(a, b);
	}

	if (a > b * 2)
	{
		return a / 2 + a % 2;
	}
	else
	{
		return (b - (a - b)) / 3 * 2 + (a - b) + (b - (a - b)) % 3;
	}
}

int xox(int a, int b)
{
	if (a < b)
	{
		swap(a, b);
	}
	return (a - b) / 2 + (a - b) % 2 + b;
}

int min_min(int a, int b)
{
	return a / 2 + a % 2 + b / 2 + b % 2;
}

void solve()
{
	int n;
	cin >> n;
	int ans = 0x3f3f3f3f;
	for (int i = 1; i <= n; ++i)
	{
		cin >> a[i];
	}
	for (int i = 2; i <= n; ++i)//相邻
	{
		ans = min(ans, adj(a[i], a[i - 1]));
	}
	for (int i = 3; i <= n; ++i)//隔1
	{
		ans = min(ans, xox(a[i], a[i - 2]));
	}
	//最小次小
	sort(a + 1, a + 1 + n);
	ans = min(ans, min_min(a[1], a[2]));
	cout << ans << '\n';
	return;
}

F. Desktop Rearrangement

传送门

【题意】给定一个二维二进制数组和一系列将给定点取反的操作,求每一次取反操作后最少要交换几次任意两点才能使数组满足「每个1的左边和上面都是1」

【思路】

朴素的想法很简单:所有「当数组满足要求后应该为1的位置」上有多少个0就需要多少次交换操作,因为每次交换可以使一个应当为1的位置由0变为1。但是每次都O(nm)遍历数组找的话显然不满足时间复杂度要求,所以需要优化。

然而优化的想法也很简单:初始时记录数组中应该为1的位置数cnt和这些位置中0的个数empty_cnt。每次取反,只要进行如下三个O(1)操作:

  1. 1. 记录数组变化
  2. 2. 由数组的变化得到cnt变化
  3. 3. 由数组的变化和cnt的变化得到empty_cnt变化

每次取反后的答案即为empty_cnt。需要注意empty_cnt的变化既与数组变化有关(应该为1的点被取反),又与cnt的变化有关(应该为1的点增加或减少,即范围变化)。

【代码】

char map[1005][1005];
int n, m, q;
int cnt = 0;
int empty_cnt = 0;

void GetCnt()
{
	for (int i = 1; i <= n; ++i)
	{
		cin >> map[i] + 1;
	}
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= m; ++j)
		{
			if (map[i][j] == '*')
			{
				cnt++;
			}
		}
	}
	return;
}

void GetEmptyCnt()
{
	for (int j = 1; j <= cnt / n; ++j)
	{
		for (int k = 1; k <= n; ++k)
		{
			if (map[k][j] == '.')
			{
				empty_cnt++;
			}
		}
	}
	for (int j = 1; j <= cnt % n; ++j)
	{
		if (map[j][cnt / n + 1] == '.')
		{
			empty_cnt++;
		}
	}
	return;
}

void solve()
{
	cin >> n >> m >> q;
	GetCnt();
	GetEmptyCnt();
	int row = 0, col = 0;
	for (int i = 1; i <= q; ++i)
	{
		cin >> row >> col;
		//改变地图
		if (map[row][col] == '*')
		{
			if (map[cnt % n ? cnt % n : n][cnt / n + !!(cnt % n)] == '.')
			{
				empty_cnt--;
			}
			map[row][col] = '.';
			cnt--;
		}
		else
		{
			cnt++;
			if (map[cnt % n ? cnt % n : n][cnt / n + !!(cnt % n)] == '.')
			{
				empty_cnt++;
			}
			map[row][col] = '*';
		}
		//计算答案
		if (col <= cnt / n || col == cnt / n + 1 && row <= cnt % n)
		{
			if (map[row][col] == '*')
			{
				empty_cnt--;
			}
			else
			{
				empty_cnt++;
			}
		}
		cout << empty_cnt << '\n';
	}
	return;
}

G. Remove Directed Edges

传送门

【题意】给定一个有向无环图和一个过程:

  • 删掉若干条边,使得图中所有出度不为0的结点出度至少减少1,所有入度不为0的结点入度至少减少1

求经过这一过程后,图的最大单向连通子图的大小

【思路】求最大单向连通子图大小其实就是求一条最长路(上的结点个数)。由于所有结点的出入度都要减少,所以只要一条路上所有结点的初始出入度(起点入度和终点出度除外)都至少为2,这条路就可能在删边过程后存在。所以只要以所有结点为起点进行记忆化dfs即可。细节见代码及注释。

【代码】

struct NODE
{
	vector<int> nxt;
	int suffix = -1;//以该点为起点的最大单向连通图
	int in = 0;//该点入度
};

int n, m;
NODE node[200005];
int ans = 0;

void dfs(int num)
{
	if (node[num].nxt.size() >= 2)//父出度≥2
	{
		for (unsigned i = 0; i < node[num].nxt.size(); ++i)//遍历子
		{
			if (node[node[num].nxt[i]].in >= 2)//子入度≥2
			{
				if (node[node[num].nxt[i]].suffix < 0)//子未被搜过
				{
					dfs(node[num].nxt[i]);
				}
			}
			else//子入度<2
			{
				node[node[num].nxt[i]].suffix = 0;
			}
			node[num].suffix = max(node[num].suffix, node[node[num].nxt[i]].suffix + 1);//由子更新父
		}
	}
	else//父出度<2
	{
		node[num].suffix = 1;
	}
	return;
}

void solve()
{
	//选出一条最长路,满足每个点出度和入度都大于等于2(边界除外)
	int u, v;
	cin >> n >> m;
	for (int i = 1; i <= m; ++i)
	{
		cin >> u >> v;
		node[u].nxt.push_back(v);
		node[v].in++;
	}
	for (int i = 1; i <= n; ++i)//注意:每个点都有机会作为起点
	{
		dfs(i);
		ans = max(ans, node[i].suffix);
	}
	cout << ans << '\n';
	return;
}

开心。

不过也是第一次掉分,简单题卡半天,还被Hack。还是太菜了,读题慢,代码功底弱,心态不够稳。继续加油吧!

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值