JZOJ(中山纪念中学) 2018.02.02【NOIP普及组】模拟赛D组

本次题目:2018.02.02【NOIP普及组】模拟赛D组

第一题

题目:第一题 公牛数字

题意:

求题目给出两个数字的乘积

分析:

这题明显只是考察学生的高精可我居然没做对,只要多练习几次,即可AC

代码:

#include<cstdio>//高精乘,各位读者自己留心下细节,小编就不多解释了
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
inline LL read() {
	LL d=0,f=1;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while(s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
	return d*f;
}
int x1[101],x2[101],w[101];
int main()
{	freopen("bullmath.in","r",stdin);
	freopen("bullmath.out","w",stdout);
	string z,s;
	cin>>z;//注意!!!一定要用cin或scanf,一旦用了getline,直接WA,本小编就是这么错的
	cin>>s;
	int l1=z.size()-1,l2=s.size()-1;
	int a=0,b=0;
	if(z[0]=='-') a++;
	if(s[0]=='-') b++;
	if(a==1&&b==0||a==0&&b==1) printf("-");
	for(int i=a;i<=l1;i++)
	  x1[l1-i+1]=z[i]-48;
	for(int i=b;i<=l2;i++)
	  x2[l2-i+1]=s[i]-48;
	int e,h;int l;
	for(int i=1;i<=l1+1;i++)
	{
		e=0;
		for(int j=1;j<=l2+1;j++)
	  	{
	  		h=(x1[i]*x2[j]+e+w[j+i-1]);
	  		w[j+i-1]=h%10;
	  		e=h/10;
		}	
		if(e>0) w[l2+1+i]=e;
	}
	int i=100;
	while(i>1&&w[i]==0) i--;
	for(int j=i;j>0;j--) printf("%d",w[j]);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

第二题

题目:愤怒的牛

题意:

求在n个牛棚中,如何安排c头牛使得相邻的两头最近的距离最远

分析:

这道题,明显不能用O(n^2),毕竟n最大可以在100,000以内,所以我们采用O(n*logn)的算法:二分+贪心
我的基本想法是这样的:先用二分算出最小距离,在每次算出后,使用贪心进行验证,如果可以,那么记录,最后输出

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
inline LL read() {
	LL d=0,f=1;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while(s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
	return d*f;
}
int n,m;
int x[100000];
bool tx(int w)
{
	int now=1,last=1,t=1;//now代表当前尝试到的点,而last代表上一次的。因为我们一定要用第一个点,所以last可以直接为1
	while(t<=m)//t为统计直到当前可以放入的牛数
	{
		while(x[now]-x[last]<w&&now<=n) now++;//当我们当前的点距离上一个点的距离小于我们通过二分枚举到的最小距离,并且还在n的范围内,那么继续贪心
		if(now>n&&t<m) return 0;//如果我们当前的点大于n但t还是没到达要求放入的牛数,那么返回假
		t++;last=now;//因为上面已经将不符合的筛掉了,那么我们现在就可以更新t和last
		if(t==m) return 1;//如果已经到达了要求牛数,那么返回真
	}
	return 1;//不进入循环,也可以返回真
}
int main()
{
	freopen("aggr.in","r",stdin);
	freopen("aggr.out","w",stdout);
	n=read();m=read();
	for(int i=1;i<=n;i++) x[i]=read();
	sort(x+1,x+1+n);//将所有的牛棚位置排序
	int l=1,r=1000000001;//二分初始化
	int mid,minm;
	while(l<=r)
	{
		mid=(l+r)>>1;//mid枚举最小距离
		if(tx(mid)//通过贪心看此次的最小距离是否可以实现) 
		  {minm=mid;l=mid+1;//minm记录当前的mid}
		else r=mid-1;
	}
	printf("%d",minm);//输出
	fclose(stdin);
	fclose(stdout);
	return 0;
}

第三题

题目:约数和

题意:

求t组数据中,每个数的约数的和

分析:

这一题,我们可以使用传统的求约数和的算法。当然这样肯定超时,所以我们可以在这个基础上加上两种优化:
(1):02优化:#pragma GCC optimize (2),虽然这种优化有点作弊的意思,且在考试时也被禁止,但不能否认其的作用之大,当然小编也极力反对用这种方法去AC题目
(2):第二种,则是记录我们已经暴力出来的数值,以后再遇到就可以直接输出,而小编就介绍一下这一种的方法

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
inline LL read() {
	LL d=0,f=1;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while(s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
	return d*f;
}
int f[5000001];
int main()
{
	int n;
	n=read();
	int a,b;
	long long t;
	for(int i=1;i<=n;i++)
	{
		a=read();
		if(f[a]!=0) {printf("%d\n",f[a]);continue;}//如果我们在这之前已经得出过这个数的答案,就直接输出
		t=0;
		b=(int)sqrt(a);//平方根,不然很容易超时
		for(int j=1;j<=b;j++)
		  if(a%j==0)
		  {
		  	t+=j;
		  	if(a/j!=j) t+=a/j;//因为我们只枚举到a的平方根,所以我们需要将其对应的另一个约数加上,当然如果a=4时,枚举到2,就会有相同的约数,所以需要判断
		  }
		printf("%d\n",t);//输出
		f[a]=t;//记录这个数的答案
	}
	return 0;
}

第四题

题目:旅行

题意:

求在增加旅店数后,我们有几种方案可以到达目的地(7000km处)

分析:

在考试时,看到这题,小编脑子里不断的浮现出各种动态转移方程,因为又是放在第四题,所以小编心有余辜,不敢尝试。但在事后上了洛谷,发现只是一道入门难度的水题。再仔细看了下题目,MMP,暴力、模拟、递推……各种算法,应有尽有,而小编则采用了递推的方式

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
inline LL read() {
	LL d=0,f=1;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while(s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
	return d*f;
}
int x[10001],ans[10001];
int main()
{
	int n,m;
	n=read();m=read();
	int a;
	a=read();
	x[1]=0;//将题目已经固定的旅店标记出来
    x[2]=990;
    x[3]=1010;
    x[4]=1970;
    x[5]=2030;
    x[6]=2940;
    x[7]=3060;
    x[8]=3930;
    x[9]=4060;
    x[10]=4970;
    x[11]=5030;
    x[12]=5990;
    x[13]=6010;
    x[14]=7000;
	for(int i=1;i<=a;i++) x[14+i]=read();//输入增加的旅店
	sort(x+1,x+15+a);//将所有旅店的位置排序
	ans[1]=1;//初始化
	for(int i=1;i<=14+a;i++)
	  for(int j=1;j<=i;j++)
	  {
	  	int b=x[i]-x[j];//得出第i个旅店与第j个旅店的距离
	  	if(b>=n&&b<=m) ans[i]+=ans[j];//如果得出的距离在n——m之间,那么累加
	  }
	printf("%d",ans[14+a]);//输出最终答案
	return 0;
}


第五题

题目:逆序统计

题意:

求有多少个符合题目特定要求的数列

分析:

这道题目需要使用dp求解。
构造f[i,j]表示(1…i)的全排列中,逆序对为j个的排列共有几个
我们首先手动模拟打表一下
f[1,0]=1 f[2,0]=1 f[3,0]=1 f[4,0]=1
f[2,1]=1 f[3,1]=2 f[4,1]=3
f[3,2]=2 f[4,2]=5
f[3,3]=1 f[4,3]=6
f[4,4]=5
f[4,5]=3
f[4,6]=1
表中有很多性质 比如:i的全排列中逆序对的个数是0…i*(i-1)/2:其实这是因为i的全排列中总共有i*(i-1)/2个数对,当这些数对全都是逆序对的时候,就得到了逆序对的最大值,
再比如:对于一个i,他的f[i,j]是关于f[i,i*(i-1)/4]呈现中心对称的(f[4,2]和f[4,4]、f[4,1]和f[4,5]、f[4,0]和f[4,6]关于f[4,3]对称):其实这是因为数对总数为i*(i-1)/2,而一个数对要么是逆序对要么是顺序对,那么我们求i的全排列中逆序对个数有j个,
等价于求i的全排列中顺序对有i*(i-1)/2-j个,显而易见,对于N的全排列 顺序对有K个这样的排列个数,和逆序对有K个这样的排列个数,是相等的于是我们有了f[i,j]=f[i, i*(i-1)/2-j]于是对称中心就是f[i,i*(i-1)/4];
最后一个重要的性质也就是我们的DP状态转移方程就是:
if i>j then f[i,j]:=sum{f[i-1,0],f[i-1,1],f[i-1,2]…f[i-1,j]}
else f[i,j]:=sum{f[i-1,j],f[i-1,j-1],f[i-1,j-2]…f[i-1,j-i+1]}
当i>j的时候,我们枚举i的全排列的第一位的数字,如果是1,那么就要求剩下i-1个数中有j个全排列,如果是2,要求剩下i-1个数中有i-2个全排列,依次类推,得到了第一个方程
当i<=j的时候,由于i的全排列中最大的数字是i,所以把i放到第一位上,由第一位最多能能产生i-1个逆序对,把1放到第一位上能产生0个逆序对,所以i-1-1+1=i-1,这时的f[i,j]就要由f[i-1,j]开始算上他自己,总共i-1项的和。
而此时,我们再对第二个方程进行一下优化,可得出:f[i][j]=f[i-1][j]+f[i-1][j-1]-f[i-1][j-i]

代码:

#include<cstdio>//由于在分析中小编已经非常详细的介绍过来,所以就给读者一个清白点的代码吧
#include<iostream>
using namespace std;
int n,k,f[110][10001];
int main()
{
	scanf("%d%d",&n,&k);
	 f[0][0]=1; 
    for(int i=1;i<=n;i++)
     for(int j=0;j<=k;j++)
      if (j)
      {
        if (i>j) f[i][j]=(f[i-1][j]+f[i][j-1])%10000; else 
		 f[i][j]=(f[i-1][j]+f[i][j-1]-f[i-1][j-i])%10000; 
      } else f[i][j]=f[i-1][j]; 
    f[n][k]=(f[n][k]+10000)%10000; 
    printf("%d",f[n][k]); 
}
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值