天天快乐编程2020年OI集训队 训练1题解

本次训练内容为贪心、二分、高精度,这些题目均为NOIP复赛原题,前4题为普及组,后3题为提高组。正式比赛可以写暴力算法骗分,作为训练需要我们把每个题目都写对。

1.4855: 排座椅

NOIP2008普及组T2
本题考察的算法为贪心,要上课时交头接耳的学生对数最少,则在最多交头接耳的学生的行或列上安排通过。
输入每一对交头接耳交头接耳的同学的位置,然后判断一下是左右相邻还是前后相邻。如果是左右相邻那这两个位置里面小的那一列就用一个数组(桶)计数器累加;如果前后相邻那这两个位置里面小的那一行就用一个数组(桶)计数器累加。
记录完成之后就要排序了,我们要输出的是行数或列数而不是每一行能够隔开的学生。所以结构体需要一个计数器和标记id的变量。这个隔完之后还需要按照id排序,id小的在前面。和2020暑假赛的结构体排序类似,需要两个排序函数。

#include <bits/stdc++.h>
using namespace std;

const int N = 1005;
//row为行 col为column缩写,列的意思
struct T
{
	int num,id;
}row[N],col[N];

bool cmp(T a,T b)
{
	return a.num > b.num;
}
bool cmp1(T a,T b)
{
	return a.id < b.id;
}
int main()
{
	int m,n,k,l,d;
	int x1,x2,y1,y2;
	cin >> m >> n >> k >> l >> d;
	//初始化id,num开的全局变量,初始值为0
	for(int i = 1;i <= m;i++)row[i].id = i;
	for(int i = 1;i <= n;i++)col[i].id = i;
	//读取d对
	for(int i = 1;i <= d;i++)
	{
		cin >> x1 >> y1 >> x2 >> y2;
		//如果是行相同,取列最小, 数量+1
		if(x1 == x2)col[min(y1,y2)].num++;
		else
		{
			//如果是列相同,取行最小,数量+1
			if(y1 == y2)row[min(x1,x2)].num++;
		}
	}
	//先对num进行排序,然后对id进行排序
	sort(col + 1,col + N,cmp);
	sort(col + 1,col + l + 1,cmp1);
	sort(row + 1,row + N,cmp);
	sort(row + 1,row + k + 1,cmp1);
	//特殊输出控制空格,正式比赛不需要
	cout << row[1].id;
	for(int i = 2;i <= k;i++)
		cout << " " << row[i].id;
	cout << "
";
	cout << col[1].id;
	for(int i = 2;i <= l;i++)
		cout << " " << col[i].id;
	cout << "
";
	return 0; 
}

2.4852: 纪念品分组

NOIP2007普及组T2
本题考察的为贪心,我们把较大的和较小的放一组可以得到最优解。
我们的贪心思路是这样的,先进行排序,从最大的数字开始枚举,如果现在最小加上最大的能装上,那就组合一组装上,否则的话就把最大的装上,给他分为单独一组,因为大的装了小的两个可能可以装上。

#include <bits/stdc++.h>
using namespace std;
const int N=30005;
int a[N],num;
int main()
{
	int n, w;
	cin >> w >> n;
	for (int i = 0; i < n; i++)cin >> a[i];
	//进行排序
	sort(a, a + n);
	//用l记录头,r记录尾部
	int l = 0, r = n - 1;
	while (l <= r)
	{
		if (a[l] + a[r] <= w)
		{
			//如果能装下,就装
			num++;
			l++, r--;
		}
		else
		{
			//不能装下,将大的分为单独的一组
			r--;
			num++;
		}
	}
	cout<<num;
	return 0;
}

3.4854: Hanoi双塔问题

NOIP2007普及组T4
这个题目需要找规律得到状态转移方程dp[i]=2 * dp[i-1] + 2
单塔和双塔有什么不同呢,双塔的步数是单塔的两倍,因为以前我们移动一个,现在移动两个。递归过程变成了
第1步:将n-2个圆盘移到B柱上。
第2步:将2个最大的圆盘移到C柱上。
第3步:将n-2个圆盘移到C柱上。
第3步的步数=第一步的步数,第2步需要2步,总步数就是第1步的步数*2+2,所以就找到状态转移方程了。
这个题n比较大,接下来写出我们的高精度代码即可。正式比赛写不出高精度请用long long骗分,不要空着。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int a[N], b[N], c[N];

//字符串类型转换为数组,并且倒过来
void stringToArr(string s, int a[])
{
	int len = s.length();
	//倒过来进行赋值
	for (int i = 0; i < len; i++)
	{
		a[i] = s[len - i - 1] - '0';
	}
}

//初始化字符串转换为数组
void init(string s1, string s2)
{
	//清0,全局变量只有第一次是0
	memset(a, 0, sizeof(a));
	memset(b, 0, sizeof(b));
	//将其转为数字串,并倒过来
	stringToArr(s1, a);
	stringToArr(s2, b);
}

//数组转换为字符串,并且倒过来
string arrToString(int a[], int n)
{
	string s = "";
	//倒过来进行字符的还原
	for (int i = n - 1; i >= 0; i--)
	{
		s += (char)(a[i] + '0');
	}
	return s;
}

//加法
string add(string s1, string s2)
{
	//先把s1和s2初始化到ab数组里
	init(s1, s2);
	//长度为两数最大的那个数
	int len = max(s1.length(), s2.length());
	//进位
	int step = 0;
	for (int i = 0; i < len; i++)
	{
		//当前位就是两数相加及进位的个位
		c[i] = (a[i] + b[i] + step) % 10;
		//当前位就是两数相加及进位的十位
		step = (a[i] + b[i] + step) / 10;
	}
	//进位不为0,所以要多一位
	if (step != 0)
		c[len++] = step;
	return arrToString(c, len);
}
string ans[205];
int main()
{
	ans[0] = "0";
	for (int i = 1; i < 205; i++)ans[i] = add(add(ans[i - 1], ans[i - 1]), "2");
	int n;
	cin >> n;
	cout << ans[n];
	return 0;
}

4.5994: 跳房子

NOIP2017普及组T4
这题考察算法为二分+单调队列优化DP,普及组并不经常考二分,但是2017年考了单调队列DP这个提高组的内容,大家可以学习一下。但是正式比赛自己一定要去尝试骗分,也就是你的check代码为暴力即可。

#include <bits/stdc++.h>
using namespace std;

const int N = 500005;
int n, m, k, d;
int x[N], num[N], dp[N], qu[N];

bool check(int len)
{
	int now = 0, head = 1, tail = 0;
	int maxlen = len + d, minlen = max(d - len, 1);
	for (int i = 1; i <= n; i++)
	{
		while (x[now] <= x[i] - minlen)
		{
			if (dp[now] <= -1e9)
			{
				//不能到达now
				now++;
				continue;
			}
			while (head <= tail && dp[now] >= dp[qu[tail]])
				tail--;
			//放进队列
			qu[++tail] = now++;
		}
		while (head <= tail && x[qu[head]] < x[i] - maxlen)
			head++;
		if (head <= tail)
			dp[i] = dp[qu[head]] + num[i];
		else
			dp[i] = -1e9;
		//有满足的直接返回
		if (dp[i] >= k)
			return 1;
	}
	return 0;
}

int main()
{
	cin >> n >> d >> k;
	for (int i = 1; i <= n; i++)
		cin >> x[i] >> num[i];
	//二分答案
	int l = 0, r = 500000;
	int ans = -1;
	while (l < r)
	{
		int mid = (l + r) >> 1;
		if (check(mid))
			r = mid, ans = r;
		else
			l = mid + 1;
	}
	cout << ans;
	return 0;
}

5.4791: 合并果子

NOIP2004提高组T2
本题考察的算法为贪心,我们需要贪心每次选择前两个,然后组合换成一个新的,可以sort,但是这样复杂度很高,我们可以用优先队列来做。

#include <bits/stdc++.h>
using namespace std;
int main()
{
	priority_queue<int, vector<int>, greater<int>> s;
	int n;
	cin >> n;
	for (int i = 0, x; i < n; i++)
	{
		cin >> x;
		s.push(x);
	}
	int ans = 0, a, b;
	for (int i = 0; i < n-1; i++)
	{
		a = s.top();
		s.pop();
		b = s.top();
		s.pop();
		ans += a + b;
		s.push(a + b);
	}
	cout << ans;
	return 0;
}

bth的暴力代码也过了

#include <bits/stdc++.h>
using namespace std;
int n,a[10001];
void min(int b)
{
	int c=b;
	for(int i=b;i<n;i++)
	{
		if(a[i]<a[b])b=i;
	}
	swap(a[c],a[b]);
}
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)cin>>a[i];
	sort(a,a+n);
	int x=0,tot=0;
	for(int i=0;i<n-1;i++)
	{
		if(i)a[i]=x;
		min(i);
		min(i+1);
		x=a[i]+a[i+1];
		tot+=x;
	}
	cout<<tot<<"
";
    return 0;
}

6.4820: 聪明的质监员

NOIP2011提高组Day2T2
本题考察算法为二分
我们可以对答案进行二分,在W取0时,所有的区间内的矿石都可以选上;而在W大于最大的质量时,所有的矿石都选不上。
W越大,矿石选的越少,W越小,矿石选的越多。
所以,随着W增大,Y值减小,满足单调性,可以二分。
我们需要计算一下区间和,直接设置i和j是n^2的,我们可以用前缀和,也就是sum[i]存储的是前i个的价值,分别对矿石价值和和钻石价值和进行前缀和,最后算的时候用右端点r-左端点l+1就可以了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 200005;
int w[N], v[N], l[N], r[N];
ll sumn[N], sumv[N];
ll s, Y, sum;
int n, m, ma = -1, mi = 0x3f3f3f3f;
bool check(int W)
{
	Y = 0, sum = 0;
	memset(sumn, 0, sizeof(sumn));
	memset(sumv, 0, sizeof(sumv));
	for (int i = 1; i <= n; i++)
	{
		if (w[i] >= W)
			sumn[i] = sumn[i - 1] + 1, sumv[i] = sumv[i - 1] + v[i];
		else
			sumn[i] = sumn[i - 1], sumv[i] = sumv[i - 1];
	}
	for (int i = 1; i <= m; i++)
		Y += (sumn[r[i]] - sumn[l[i] - 1]) * (sumv[r[i]] - sumv[l[i] - 1]);

	sum = abs(Y - s);
	if (Y > s)
		return 1;
	else
		return 0;
}
int main()
{
	cin >> n >> m >> s;
	for (int i = 1; i <= n; i++)
	{
		cin >> w[i] >> v[i];
		ma = max(ma, w[i]);
		mi = min(mi, w[i]);
	}
	for (int i = 1; i <= m; i++)
		cin >> l[i] >> r[i];
	//二分答案
	int left = mi - 1, right = ma + 2, mid;
	//ans设置一个最大值
	ll ans = 0x3f3f3f3f3f3f3f3f;
	while (left <= right)
	{
		mid = (left + right) >> 1;
		if (check(mid))
			left = mid + 1;
		else
			right = mid - 1;
		if (sum < ans)
			ans = sum;
	}
	cout << ans;
	return 0;
}

7.4824: 国王游戏

NOIP2012提高组Day1T2
本题考察算法为贪心+高精度
对于第 i个大臣和第 j 个大臣:
如果第 i 个大臣放第 j 个大臣前面对答案的贡献小些,那么第 i 个大臣就放第 j个大臣前面
所以就是使a[i].x / a[j].y<a[j].x / a[i].y
所以就是 a[i].x * a[i].y<a[j].x * a[j].y

#include<bits/stdc++.h>
using namespace std;
//使用10000进行压位
const int jz=10000;
struct T
{
    int a,b;
}p[1005],king;
bool cmp(T a,T b)
{
    return a.a*a.b<b.a*b.b;
}
int a[jz];
void mul(int n)
{
    int t=0;
	for(int i=0;i<jz;i++)
    {
		t+=a[i]*n;
		a[i]=t%jz;
		t/=jz;
	}
}
void div(int n)
{
    int t=0;
    for(int i=jz-1;i>=0;i--)
    {
        t=a[i]+t*jz;
    	a[i]=t/n;
        t%=n;
    }
}
int main()
{
    int n;
    cin>>n>>king.a>>king.b;
    for(int i=0;i<n;i++)cin>>p[i].a>>p[i].b;
    sort(p,p+n,cmp);
    a[0]=king.a;
    for(int i=0;i<n-1;i++)mul(p[i].a);
    div(p[n-1].b);
    int t=jz-1;
	//去除前导0
    while(t>=0&&!a[t])t--;
    if(t==-1)
    {
        printf("1
");
        return 0;
    }
    printf("%d",a[t]);
	//以1000进行压位,所以需要输出%04d
    for(int i=t-1;i>=0;i--)printf("%04d",a[i]);
}
原文地址:https://www.cnblogs.com/BobHuang/p/13617793.html