• 天天快乐编程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]);
    }
    
  • 相关阅读:
    [NOIP2002 提高组] 均分纸牌
    洛谷 P1303 A*B Problem
    OpenJudge 1.6.5 年龄与疾病
    hdu 3340 线段树思路活用
    poj 2464 线段树统计区间..两棵树
    hdu 4419 矩形面积覆盖颜色
    经典动态规划 dp Rqnoj 57
    最基础二维线段树 hdu 1823 (简单)
    hdu 3564 线段树+dp
    spoj 1557 线段树 区间最大连续和 (不重复数)
  • 原文地址:https://www.cnblogs.com/BobHuang/p/13617793.html
Copyright © 2020-2023  润新知