加载中...

SDTBU-ACM集训队暑期集训---个人赛 10


前言

过了样例不要着急,多测几组自己的数据,错一次20分钟的罚时还是很伤的。

第一题

题目: Changing Volume

题意:

给我们两个数a和b,每次可以在a上加 [-5,-2,-1,1,2,5] 六个数之一,问最少几次可以将a变为b。

输入格式

输入包含多个样例,第一行是一个整数t,表示有t个样例。

每个样例包含两个整数a和b。

输出格式

对每个样例输出一个整数。

数据范围

1 ≤ t ≤ 1000

1 ≤ a, b ≤ 109

输入样例:

3
4 0
5 14
3 9

输出样例:

2
3
2

思路:

  • 思维题。因为可操作的六个数是对称的,所以我们可以将a变为b视为将a和b中较小的数变为较大的数。
  • 从5到1每次贪心减去最多能减的数即可。

代码:

#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1e5 + 10;

int d[] = { 1,2,5 };

int get_num(int a, int b)
{
	if (a > b)swap(a, b);
	int num = 0;
	for (int i = 2; i >=0; --i)
	{
		if (b == a)break;
		int t = (b - a) / d[i];
		num += t;
		b -= d[i]*t;
	}
	return num;
}

int main()
{
	int n;
	cin >> n;
	while (n--)
	{
		int a, b;
		cin >> a >> b;
		cout << get_num(a, b) << endl;
	}
	return 0;
}

第二题

题目: Fridge Lockers

题意:

有n个住户,每个住户有一个房间,住户自己可以开自己的房间,每个房间会有一个代价。我们可以将n个房间上m把锁,每把锁会把两个房间连在一起。如果一个房间通过两把锁以上连接了另外两个以上不同的房间,那么这些房间的主人可以共同打开这个房间。但是代价是这些锁所连接的房间的代价之和。可以在两个房间之间上多把锁,问在没有任何一个房间是私有的前提下(私有是指只有房间主人可以打开该放房间),打开所有房间的最小代价。感觉我描述的好繁琐晦涩,但是原文比这还难理解啊,起码这算是人话了

输入格式

输入包含多个样例,第一行是一个整数t,代表有t个样例。

每个样例第一行是两个整数n和m,表示有n个房间和可以上m把锁。

每个样例第二行是n个整数,表示每个房间的代价。

输出格式

对于每个样例,如果不存在输出-1

否则第一行输出一个整数,表示总代价。

接下来m行每行两个整数,表示该锁所连接的两个房间。

数据范围

1 ≤ t ≤ 10

2 ≤ n ≤ 1000, 1 ≤ m ≤ n

0 ≤ ai ≤104

保证所有的n之和不会大于2×105

输入样例:

3
4 4
1 1 1 1
3 1
1 2 3
3 3
1 2 3

输出样例:

8
1 2
4 3
3 2
4 1
-1
12
3 2
1 2
3 1

思路:

  • 首先要满足所有房间都不是私有的,也就是说任何房间都起码有两把锁和两个不同的房间相连,那么n个房间最少需要n把锁。同时如果只有两个房间,是无法实现非私有化的。
  • 如果满足 m>=n ,首先为了满足非私有化需要所有房间两两上锁,然后将多余的锁都上在代价最小的两个房间即可。

代码:

#include<iostream>
#include<algorithm>
#include<queue>

using namespace std;
const int N = 1e4 + 10;

int a[N];

int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		int m, n;
		cin >> n >> m;
		int num = 0;
		priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>>p;
		for (int i = 1; i <= n; ++i)
		{
			cin >> a[i];
			num += a[i];
			p.push({ a[i],i });
		}
		num *= 2;
		if (m < n||n==2)
		{
			cout << -1 << endl;
			continue;
		}
		else
		{
			sort(a + 1, a + n + 1);
			num += (a[1] + a[2]) * (m - n);
			cout << num << endl;
			for (int i = 1; i < n; ++i)cout << i << " " << i + 1 << endl;
			cout << n << " "<<1 << endl;
			int x = p.top().second;
			p.pop();
			int y = p.top().second;
			p.pop();
			for (int i = n+1; i <= m; ++i)cout << x << " " << y << endl;
		}
	}
	return 0;
}

第三题-赛后补题

题目: League of Leesins

题意:

有n个整数组成的数列,每三个相邻的三个整数可以组成一个三元组,可以任意交换三元组内的元素和不同组的顺序,但是不能交换不同组内的元素,求出原序列。

输入格式

输入第一行是一个整数n表示原数列有n个整数。

接下来有 n-2 行,每行是一个三元组。

输出格式

输出一行n个整数,两个整数之间用空格间隔。

数据范围

5 ≤ n ≤ 105

1 ≤ qi, qj ≤ n

输入样例:

5
4 3 2
2 3 5
4 1 2

输出样例:

1 4 2 3 5 

思路:

  • 首先我们可以会发现,原数列的开始位置和结束位置的数字都只出现了一次,第二个和倒数第二个数字都只出现了二次,中间所有数字都出现了三次。
  • 因为正序输出还是倒序输出都是可接受的,所以我们可以先随便选一个只出现一次的数字作为起始位置,然后找到和它一组的另外两个元素,判断一下出现两次或者三次,起始位置先输出出现两次的,结束位置后输出出现两次的。而确定两个数之后,可以根据前两个数来找到后面那个元素,因为相邻三个数一定在一个三元组中。
  • 这就涉及到一个问题,怎么判断两个三元组有两个元素相同,暴力遍历最终会超时。
  • 考虑如何优化,如果通过三元组的关系将所有点画成图,我们会发现所谓出现的次数其实对应着每个点的入度,那么就可以用拓扑排序的做法来解决。需要注意的是在移去某个点时,可能会导致倒数第三个点和倒数第二点的入度同时变为1,为了使顺序不变,我们可以先将所有点根据初始入度排序。

超时代码:

#include<iostream>
#include<algorithm>
#include<queue>
#include<unordered_map>

using namespace std;
const int N = 1e5 + 10;

int a[N][4],b[4];
unordered_map<int, int>p;

int judge(int j)
{
	unordered_map<int, int>p;
	for (int i = 1; i <= 3; ++i)
	{
		p[b[i]]++;
		p[a[j][i]]++;
	}
	int res = 0, num = 0;
	for (auto t : p)
	{
		if (t.second == 1)
		{
			res++;
			for (int k = 1; k <= 3; ++k)if (t.first == a[j][k])num = k;
		}
	}
	if (res == 2)return num;
	else return 0;
}

int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n - 2; ++i)
	{
		for (int j = 1; j <= 3; ++j)
		{
			cin >> a[i][j];
			p[a[i][j]]++;
		}
	}
	int x=0,y=0;
	for (int i = 1; i <= n-2; ++i)
	{
		if (x)break;
		for (int j = 1; j <= 3; ++j)
			if (p[a[i][j]] == 1)x = i, y = j;
	}

	a[x][0] = 1;
	cout << a[x][y] << " ";
	pair<int, int>s[5];
	for (int i = 1; i <= 3; ++i)
	{
		b[i] = a[x][i];
		s[i].first = p[a[x][i]]; s[i].second = a[x][i];
	}
	sort(s + 1, s + 4);
	cout << s[2].second << " " << s[3].second << " ";
	for (int i = 1; i <= n - 3; ++i)
	{
		for (int j = 1; j <= n - 2; ++j)
		{
			if (!a[j][0])
			{
				int f = judge(j);
				if (f)
				{
					a[j][0] = 1;
					cout << a[j][f] << " ";
					for (int k = 1; k <= 3; ++k)b[k] = a[j][k];
					break;
				}
			}
		}
	}
	return 0;
}

拓扑排序代码

#include<iostream>
#include<algorithm>
#include<queue>
#include<unordered_map>
#include<vector>

using namespace std;
const int N = 1e5 + 10;

vector<int>v[N];
unordered_map<int, int>p;

bool find(int a, int b)
{
	//如果数组v[a]已经存在b,那么就不用再存
	for (int i = 0; i < v[a].size(); ++i)if (v[a][i] == b)return false;
	
	//说明没有出现过,需要存b
	return true;
}

//建立一个在vector中a为下标的数组,存的数是和a有关的所有三元组的其它元素。
void rudu(int a, int b, int c)
{
	if (find(a,b))v[a].push_back(b);
	if (find(a, c))v[a].push_back(c);
}

bool cmp(int a, int b)
{
	return p[a] > p[b];
}

int main()
{
	int n;
	cin >> n;
	int m = n - 2;
	while (m--)
	{
		int a, b, c;
		cin >> a >> b >> c;
		p[a]++, p[b]++, p[c]++;
		rudu(a, b, c), rudu(b, a, c), rudu(c, a, b);
	}

	//将入度大的排在前面,防止倒数第二个和倒数第三个入度同时变为1,导致顺序混乱。
	for (int i = 1; i <= n; ++i)sort(v[i].begin(), v[i].end(), cmp);
	
	//找到起始位置和结束位置
	int l=0, r=0;
	for(int i=1;i<=n;++i)
		if (p[i] == 1)
		{
			if (l == 0)l = i;
			else r = i;
		}
	
	queue<int>q;
	q.push(l);
	while (q.size())
	{
		int t = q.front();
		q.pop();
		cout << t << " ";
		for (int i = 0; i < v[t].size(); ++i)
		{
			//当三元组的某个元素出队后,与之相关的元素入度都要减一
			p[v[t][i]]--;
			if (p[v[t][i]] == 1)q.push(v[t][i]);
		}
	}
	//不要忘记输出最后一个元素
	cout << r;
	
	return 0;

}

文章作者: 心意
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 心意 !
评论
  目录