NOIP 2016模拟赛[八中题]题解&总结

这次比赛还是暴露出了自己的一些不足之处,比如比赛经验以及心态,还有某些知识点的缺失等等

总的来说发挥正常,就是第二题越做越慌,但是最后还是克服了这一心态,还是非常不错的

以后开始慌了就上厕所


A.分队问题
给定n 个选手,将他们分成若干只队伍。其中第i 个选手要求自己所属的队伍的人数大等于a[i]人。
在满足所有选手的要求的前提下,最大化队伍的总数。
注:每个选手属于且仅属于一支队伍。
输入格式
第一行一个整数n,表示人数。
以下n 行,每行一个整数表示a[i]。
输出格式
输出队伍总数的最大值。数据保证有解。


样例输入
5
2
1
2
2
3


样例输出
2


数据范围
对于20%的数据,n <= 10
对于40%的数据,n <= 1000
对于60%的数据,n <= 10000
对于100%的数据,1 <= n <= 10^6


这个题属于一眼题,但是很多人都会想到贪心,我开始也想的贪心:从大到小排序,然后尽量选大的。

这个贪心在测试的时候确实可以拿到80分,但是实质上是错误的,观察下面的数据:

10

1 1 1 3 6 6 6 6 6 6

如果贪心,分法就为:[1][113][666666]一共3组,但是实际上我们还有更优的方法:[1][1][1][3666666]一共4组,因此普通的贪心就是错的(当然改良版贪心是可以过的,不过不知道怎么写)

既然贪心不行,那么就试试分组DP,显然DP是可以过的

f[i]表示使得前i个人分组合法的组数最大值,f[i]=min{f[k]}+1 0<=k<=i-s[i]

但是不加任何优化的DP是拿不了满分的,显然复杂度为O(N^2)

f[i]=g[i–s[i]]+1;
g[i]=max(g[i-1],f[i]);

可以看到用g数组优化了f数组

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=1e6+5,inf=x3f3f3f3f;
inline void _read(int &x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9')
    {if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}
int n,f[maxn],s[maxn],g[maxn];
int main(){
	_read(n);
	int i,j,cur;
	for(i=1;i<=n;i++)_read(s[i]);
	sort(s+1,s+1+n);
	for(i=1;i<=n;i++){
		if(i<s[i]){g[i]=g[i-1];continue;}
		cur=i-s[i];
		if(cur>i-1)cur=i-1;
		f[i]=g[cur]+1;
		g[i]=max(g[i-1],f[i]);
	}
	cout<<f[n];
}

B.子矩阵
小A 有一个N×M 的矩阵,矩阵中1~N*M 这(N*M)个整数均出现过一次。现在小A 在这个矩阵内选择一个子矩阵,其权值等于这个子矩阵中的所有数的最小值。小A 想知道,如果他选择的子矩阵的权值为i(1<=i<=N×M),那么他选择的子矩阵可能有多少种?小A 希望知道所有可能的i 值对应的结果,但是这些结果太多了,他算不了,因此他向你求助。
输入格式
第一行,两个整数N, M。
接下来的N 行,每行M 个整数,表示矩阵中的元素。
输出格式
N×M 行,每行一个整数,其中第i 行的整数表示如果小A 选择的子矩阵权
值为i,他选择的子矩阵的种类数。


输入样例
2 3
2 5 1
6 3 4


输出样例
6

4

5

1

1

1


数据范围
对于30%的数据,1<=N, M<=50;
对于全部的数据,1<=N, M<=300。


看到这题想到了无数做法但是都写不来,暴力的暴力都是O(N^6)

其实我们将矩阵看成1维线段就是一个广告印刷模型了,只要枚举矩阵上下界然后用一个数组保存下界每一列的最小值

接下来依次考虑min中每个元素作为子矩阵的最小值元素时,可以向左和向右扩展到多远。
以向右扩展为例。某一列k的最小值min[k]不会影响k左边比它小的元素向右扩展,但会影响左边比它大的元素向右扩展,用一个单调栈维护一个上升序列,可以高效处理。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<stack>
using namespace std;
const int maxn=305,inf=0x3f3f3f3f;
int n,m,s[maxn][maxn],miny[maxn],ans[maxn*maxn],L[maxn],R[maxn];
stack<int>q;
inline void _read(int &x){ 
    char t=getchar();bool sign=true; 
    while(t<'0'||t>'9') 
    {if(t=='-')sign=false;t=getchar();} 
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0'; 
    if(!sign)x=-x; 
} 
int main(){
	_read(n);_read(m);
	int i,j,k;
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)_read(s[i][j]);
	for(i=1;i<=n;i++){
		memset(miny,inf,sizeof(miny));
		for(j=i;j<=n;j++){
			for(k=1;k<=m;k++)miny[k]=min(miny[k],s[j][k]);
			for(k=1;k<=m;k++){
				while(q.size()&&miny[k]<miny[q.top()]){
					R[q.top()]=k-1;
					q.pop();
				}
				q.push(k);
			}
			while(q.size()){
				R[q.top()]=m;
				q.pop();
			}
			for(k=m;k;k--){
				while(q.size()&&miny[k]<miny[q.top()]){
					L[q.top()]=k+1;
					q.pop();
				}
				q.push(k);
			}
			while(q.size()){
				L[q.top()]=1;
				q.pop();
			}
			for(k=1;k<=m;k++)
				ans[miny[k]]+=(R[k]-k+1)*(k+1-L[k]);
		}
	}
	for(i=1;i<=n*m;i++)
		printf("%d\n",ans[i]);
}

C.数字对
对于一个数字对(a, b),我们可以通过一次操作将其变为新数字对(a+b, b)或(a,a+b)。
给定一正整数n,问最少需要多少次操作可将数字对(1, 1)变为一个数字对,该数字对至少有一个数字为n。
输入格式
第一行一个正整数n
输出格式
一个整数表示答案。


样例输入
5


样例输出
3


样例解释
(1,1) → (1,2) → (3,2) → (5,2)
数据范围
对于30%的数据, 1 <= n <= 1000
对于60%的数据, 1 <= n <= 20000
对于100%的数据,1 <= n <= 10^6


这题爆搜加斐波拉契数列优化可以过70分,一神人同学用爆搜+rand骗分过了90分……

正解是类似于GCD的递归求解,其实说了正解就很简单了,注意一下边界处理即可

#include<cstdio>
#include<iostream>
using namespace std;
const int inf=0x3f3f3f3f;
int n,ans;
int Div(int x,int y){
	if(y==1)return x-1;
	if(x==0||y==0||x==y)return inf;
	return Div(y,x%y)+x/y;
}
int main(){
	cin>>n;
	ans=n-1;
	for(int i=2;i<n;i++)ans=min(Div(n,i),ans);
	cout<<ans;
}
最后再说两句:

还是趁着最后几个星期补一下遗忘的知识点,小分绝不能丢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值