2023/3/30总结

题解

Problem - C - Codeforces

1.这一题是一个贪心问题。

2.我们希望成本尽可能贴近之前的值,题目说过如果是4,4,1,4,4标签是3个,只会把相邻的数字作为一个标签。成本价必须是价格的倍数,而且成本价也必须是总价的约数。

3.那么这个问题就是要找到总价的最大公约数和糖果价格的最小公倍数。如果前一次总价的最大公约数同时可以将当前的糖果数量除尽,那么我们就不需要额外的标签,否则就需要额外的标签。

4.第一行20 3总价为60,价格为3 此时最小公约数是它本身60,公倍数是3,到了第二行就是6*2总价看是否能除尽上一次的3,如果能说明可以在一个标签内。我们还需要刷新最小公倍数。

5.如果除不尽,那么就需要重新存储。另外一个点是,第一个出现的糖果数字,最好先存,可能会出现除数为0的情况。至于求最大公约数是辗转相除法,最小公倍数=a*b/最大公约数。还有开long long,十年OI一场空,不开long long见祖宗。

#include<stdio.h>
#define N 200010

long long a[N][2],b[N][2];
long long yueshu(long long x,long long y)
{
	long long a,b,r;
	a=x,b=y;
	r=a%b;
	while(r)
	{
		a=b;
		b=r;
		r=a%b;
	}
	return b;
}
long long beishu(long long x,long long y)
{
	long long a,b,r;
	a=x,b=y;
	r=a%b;
	while(r)
	{
		a=b;
		b=r;
		r=a%b;
	}
	return x*y/b;
}
int main()
{
	long long t,n,i,j;
	long long count,curmax,curmin,tmax,tmin;
	scanf("%lld",&t);
	while(t--)
	{
		count=0;
		scanf("%lld",&n);
		for(i=0;i<n;i++)
		{
			scanf("%lld%lld",&a[i][0],&a[i][1]);
			//糖果和糖果价格
		}
		b[0][0]=a[0][0]*a[0][1];
		b[0][1]=a[0][1];
		for(i=1;i<n;i++)
		{
		//	puts("*");
			curmin=a[i][1];
			curmax=a[i][0]*a[i][1];
			tmax=yueshu(curmax,b[count][0]);
			tmin=beishu(curmin,b[count][1]);
		//	printf("%d %d\n",tmax,tmin);
			if(tmax%tmin!=0)
			{
				count++;
				b[count][0]=a[i][0]*a[i][1];
				b[count][1]=a[i][1];
			}
			else 
			{
				b[count][0]=tmax;
				b[count][1]=tmin;
			}
		}
		printf("%lld\n",count+1);
	}
	return 0;
}

Problem - D - Codeforces

 1.这个题目我们只要保证前面的数字总和的绝对值不为max-min即可。

2.总体思路是分成俩类——正数和负数,如果超过max-min就放入一个负数,再去放入正数。如果其中一份放完了,那么结束循坏去看,放入接下来所有的正数或者负数是否还能满足条件即可。

3.需要注意的是,abs函数返回值是int,我们必须自己写绝对值,需要开long long存储。

代码如下:

#include<stdio.h>
#include<math.h>
#define N 300010
long long a[N];
long long zs[N];
long long fs[N];
long long res[N];
long long l1,l2;
int main()
{
	long long t,n,i,j,max,min,k,curnum;
	scanf("%lld",&t);
	while(t--)
	{
		l1=l2=0;
		max=-1;
		min=999999999;
		curnum=0;
		scanf("%lld",&n);
		for(i=0;i<n;i++)
		{
			scanf("%lld",&a[i]);
			if(max<a[i]) max=a[i];
			if(min>a[i]) min=a[i];
			if(a[i]>=0) zs[l1++]=a[i];
			else fs[l2++]=a[i];
		}
		k=0;
		for(i=0,j=0;i<l1&&j<l2;)
		{
			if(curnum+zs[i]<0) curnum=-(curnum+zs[i]);
			if(abs(curnum+zs[i])<max-min)
			{
				curnum+=zs[i];
				res[k++]=zs[i];
				i++;
			}
			else 
			{
				if(curnum+fs[j]<0) curnum=-(curnum+fs[j]);
				if(abs(curnum+fs[j])<max-min)
				{
					curnum+=fs[j];
					res[k++]=fs[j];
					j++;
				}
			}
		}
		while(i<l1) 
		{
			if(abs(curnum+zs[i])<max-min) 
			{
				res[k++]=zs[i];
				i++;
			}
			else break;
		}
		if(i<l1) 
		{
			puts("No\n");
			continue;
		}
		while(j<l2)
		{
			if(abs(curnum+fs[j])<max-min)
			{
				res[k++]=fs[j];
				j++;
			}
			else break;
		}
		if(j<l2)
		{
			puts("No\n");
			continue;
		}
		puts("Yes");
		for(i=0;i<n;i++)
		{
			printf("%lld ",res[i]);
		}
		puts("");
	}
	return 0;
}

 线段树OR树状数组 - Virtual Judge (vjudge.net)

1.这是一个线段树的题目,可以说是模板题目。

2.如果去暴力肯定回爆,我们将区间二分成一棵树,然后去查找求值即可。

代码如下:

#include<stdio.h>
#include<string.h>
#define N 50010
char str[100];
int a[N],n,sum=0;
struct node
{
	int l;
	int r;
	int sum;
}arr[N*100];
int creat(int left,int right,int k)
{
	if(left>right) return 0;
	if(left==right) 
	{
		arr[k].l=left;
		arr[k].r=right;
		arr[k].sum=a[left];
		return 0;
	}
	else 
	{
		int mid=(left+right)/2;
		arr[k].l=left;
		arr[k].r=right;
		creat(left,mid,2*k);
		creat(mid+1,right,2*k+1);
		arr[k].sum=arr[2*k].sum+arr[2*k+1].sum;
		
	}
}
int search(int k,int l,int r)//找一段区间的和
//left现在遍历到的位置,right现在遍历到的右边
//l,r所求的区间
{
	if(l>r) return 0;
	if(arr[k].l==l&&arr[k].r==r) 
	{
		sum+=arr[k].sum;
		return 0;
	}
	int mid=(arr[k].l+arr[k].r)/2;
	//先算出在左子树还是右子树
	if(mid>r) 
	{
		//在左子树
		search(k*2,l,r);
	}
	else if(mid<l)
	{
		//右子树
		search(k*2+1,l,r);
	}
	else //在中间,分开俩半
	{
		search(k*2,l,mid);//左子树
		search(k*2+1,mid+1,r);//右子树
	}
}
int change(int x,int add,int k)
{
	if(arr[k].l>x) return 0;
	if(arr[k].r<x) return 0;
	if(arr[k].l==x&&arr[k].r==x)
	{
		arr[k].sum+=add;//如果找到了
		return 0;
	}
	change(x,add,k*2);//遍历左子树
	change(x,add,k*2+1);//遍历右子树
	arr[k].sum=arr[k*2].sum+arr[2*k+1].sum;//计算出值
}
int main()
{
	int t,i,j,x,y;
	scanf("%d",&t);
	for(i=1;i<=t;i++)
	{
		scanf("%d",&n);
		for(j=1;j<=n;j++)
		{
			scanf("%d",&a[j]);
		}
		creat(1,n,1);
		printf("Case %d:\n",i);
		while(1)
		{
			scanf("%s",str);
			if(strcmp(str,"Query")==0)
			{
				scanf("%d%d",&x,&y);
				sum=0;
				search(1,x,y);
				printf("%d\n",sum);
			}
			else if(strcmp(str,"Add")==0)
			{
				scanf("%d%d",&x,&y);
				change(x,y,1);
			}
			else if(strcmp(str,"Sub")==0)
			{
				scanf("%d%d",&x,&y);
				change(x,-y,1);
			}
			else if(strcmp(str,"End")==0)
			{
				break;
			}
		/*	for(i=1;arr[i].sum;i++)
			{
				printf("%d ",arr[i].sum);
			}*/
		}
	}
}

 算法知识——线段树

线段树方便区间查询和修改。

建立线段树

用到了堆的知识,父节点的子节点下标为2*i和2*i+1

区间为1-5,我们去建立的时候,会二分成1-3,4-5,直到只有一个元素,往下递归建立,建立完之后会回溯去取得总和sum。

int creat(int left,int right,int k)
{
	if(left>right) return 0;
	if(left==right) 
	{
		arr[k].l=left;
		arr[k].r=right;
		arr[k].sum=a[left];
		return 0;
	}
	else 
	{
		int mid=(left+right)/2;
		arr[k].l=left;
		arr[k].r=right;
		creat(left,mid,2*k);
		creat(mid+1,right,2*k+1);
		arr[k].sum=arr[2*k].sum+arr[2*k+1].sum;
		
	}
}

 查找

知道所要查询的值,去缩小范围去查询即可。

int search(int k,int l,int r)//找一段区间的和
//left现在遍历到的位置,right现在遍历到的右边
//l,r所求的区间
{
	if(l>r) return 0;
	if(arr[k].l==l&&arr[k].r==r) 
	{
		sum+=arr[k].sum;
		return 0;
	}
	int mid=(arr[k].l+arr[k].r)/2;
	//先算出在左子树还是右子树
	if(mid>r) 
	{
		//在左子树
		search(k*2,l,r);
	}
	else if(mid<l)
	{
		//右子树
		search(k*2+1,l,r);
	}
	else //在中间,分开俩半
	{
		search(k*2,l,mid);//左子树
		search(k*2+1,mid+1,r);//右子树
	}
}

 修改

修改有俩种修改,单点修改,目前只会单点修改。多点修改牵扯到标记。

单点修改就是在查询的基础上去改变值,回溯的时候也要一起去回溯。

int change(int x,int add,int k)
{
	if(arr[k].l>x) return 0;
	if(arr[k].r<x) return 0;
	if(arr[k].l==x&&arr[k].r==x)
	{
		arr[k].sum+=add;//如果找到了
		return 0;
	}
	change(x,add,k*2);//遍历左子树
	change(x,add,k*2+1);//遍历右子树
	arr[k].sum=arr[k*2].sum+arr[2*k+1].sum;//计算出值
}

多点修改目前只会一些概念——懒惰标记,在需要修改的区间上,去加一个结点存储我们修改所产生的值,下次如果需要遍历到它的子节点,记下懒惰标记,改子节点即可。 

java知识点

异常处理:

java把所有的非正常情况分成俩种Eorror和Exception都是继承Throwable类。

异常有俩种运行时异常编译时异常

IndexOutOfBoundsException  数组越界异常

NumberFormatException 输入的参数不是数字而是字母,数字格式异常

ArithmeticException 除数为0异常

NullPointerException  空指针异常

try-catch-finally语句

try
{


}
catch(Exception e)
{
        //

}
finally
{

}

当try块区域出现异常时,系统会自动生成一个异常对象,该对象被提交给java允许时环境,这个过程被称为抛出异常。

catch语句是捕获到了异常,catch语句可以写多条语句,针对不同的异常。try语句后面的花括号是不能够省略的。

finally语句后面是无论如何都会执行的语句,通常用来执行出现异常的一些必要的操作。

捕获多种类型的异常时多种异常类型之间用竖线隔开

捕获多种类型的异常时,异常变量有隐式的final修饰

访问异常信息

getMessage()返回异常详描述字符串

printStackTrace()将该异常的跟踪栈信息输出到标准错误输出

printStackTrace(PrintStream)将该异常的跟踪栈信息输出到指定的输出流

getStackTrace()返回该异常的跟踪栈信息

不能在finally块中使用return语句或者throw语句,否则会导致try、catch语句里面的对应语句失效

try关键字后面紧跟一对圆括号,圆括号可以声明、初始化一个或者多个资源,这些资源必须是在程序结束时显示关闭的资源,try语句会在该语句结束时自动关闭这些资源。

可以被自动关闭的资源类要么实现AutoCloseable接口要么实现Closeable接口。

不是RuntimeException类及其子类的异常实例都是编译异常。

如果明确指定如何处理该异常,应该使用try、catch语句

如果不知道则抛出。

throws

使用throws抛出异常,由上一级调用者去处理,如果main方法也不知道如何处理,也可以抛出让JVM处理,JVM处理异常的方法是打印异常的跟踪栈信息,并且中止程序运行。

throws声明抛出只能在方法签名中使用,throws可以声明抛出多个异常类,逗号隔开。

调用者需要try、catch或者也抛出。

throws声明抛出异常时,需要保证子类方法声明抛出的异常类型必须时父类方法声明类型的子类或者相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。

对于编译时异常,java要求必须显式捕获并且处理或者抛出。

使用throw抛出异常

自行抛出异常使用throw语句来完成。

throw语句抛出的不是异常类,而是一个异常实例。

自定义异常类

用户自定义的异常类应该都继承于Exception类,最好继承RuntimeException类,需要俩个构造器,一个是无参的一个是带字符串参数的构造器,用于描述异常对象的信息。

网络编程

因为在高中已经学过一些计算机网络知识,关于TCP/IP协议以及其他就不在此赘述了。

InetAddresss类

static getByName()确定主机名称,可以是ip地址,也可以是电脑主机名称

String getHostName()获取IP地址的主机名称

String getHostAddress()获取文本显示中的IP地址字符串

DatagramSocket类

这是一个插座,遵循UDP协议。

DatagramSocket()无参时,绑定本地主机的任何可用的端口,有一个参数,绑定本地主机的指定端口,有俩个参数是绑定到指定的本地(不一定是本地主机)套接字地址。

方法:

getPort()返回该对象绑定的本地主机的端口号

close()关闭该数据报套接字

send(DatagramPacket dp)接受数据报

receive(DatagramPacket dp)发送数据报

Datagrampacket类

DatagramPacket(byte[] buf,int length)用来接收长度为length的buf数组数据

DatagramPacket(byte[] buf,int length,InetAddress address,int port)将length长度的buf数据发送到指定的地址的端口号处

DatagramPacket(byte [] buf,int  length,SocketAddress address) 将length长度的buf数据发送到指定的套接字地址处。

(先运行接收方,再运行发送方)

MulticastSocket类

用于组播

MulticastSocket()无参是本机地址随机端口,有一个参数是本机地址指定端口,有俩个参数是指定ip地址和指定端口。

接受和发送数据和DatagramSocket是一样的。

setTimeToLive(int ttl)方法代表了设置数据报最多可以跨过多少个网络。

joinGroup(InetAddress address)代表加入到一个组播组

leaveGroup(InetAddress address)代表离开一个组播组

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值