本次训练内容为贪心、二分、高精度,这些题目均为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]);
}