P1001 A+B Problem (良心题解)

这题不是线段树的模板题吗,其实可以这样理解QAQ:
A+B=(A/gcd(A,B)+B/gcd(A,b))*gcd(A,B) (gcd指最大公约数)
所以100+的线段树就出现了

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string.h>
using namespace std;
//为了防止超时,先写一个读入输出优化
int read(void)//读入优化
{
	int x=0;int w=1; char ch=0;
	while(!isdigit(ch)){if(ch=='-')w=-1;if(ch=='+')w=1;ch=getchar();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return w*x;
}
void write(int x)//输出优化
{
	if(x<0){putchar('-');x=-x;}
	if(x>9)write(x/10);	
	putchar(x%10+'0');
}
int Left(int a){return a*2;}
int Right(int a){return a*2+1;}
int i,j,k,m,n,a[1000001],ans[1000001],Lazy[1000001],lazy[1000001];
void PushUp(int now)//记录答案
{
	ans[now]=(ans[Left(now)]+ans[Right(now)]);
}
void Build(int now,int l,int r)//建树
{
	if(l==r){ans[now]=a[l];return;}
	int mid=(l+r)/2;
	Build(Left(now),l,mid);
	Build(Right(now),mid+1,r);
	PushUp(now);
}
void Function(int now,int l,int r,int Add)//加法的运算
{
	Lazy[now]+=Add;
	ans[now]+=Add*(r-l+1);
}
void PushDown(int now,int l,int r)//向下推懒标记-加法
{
	int mid=(l+r)/2;
	Function(Left(now),l,mid,Lazy[now]);
	Function(Right(now),mid+1,r,Lazy[now]);
	Lazy[now]=0;
}
void function(int now,int l,int r,int Add)//惩罚的运算
{
	Lazy[now]*=Add;
	lazy[now]*=Add;
	ans[now]*=Add;
}
void Push_Down(int now,int l,int r)//向下推懒标记-乘法
{
	int mid=(l+r)/2;
	function(Left(now),l,mid,lazy[now]);
	function(Right(now),mid+1,r,lazy[now]);
	lazy[now]=1;
}
void Data(int NowL,int NowR,int l,int r,int now,int Add)//对于某个区间的数都加上一个数
{
	if(NowL<=l&&NowR>=r)
	{
		ans[now]+=Add*(r-l+1);
		Lazy[now]+=Add;
		return;
	}
	Push_Down(now,l,r);
	PushDown(now,l,r);
	int mid=(l+r)/2;
	if(NowL<=mid)Data(NowL,NowR,l,mid,Left(now),Add);
	if(NowR>mid)Data(NowL,NowR,mid+1,r,Right(now),Add);
	PushUp(now);
}
void data(int NowL,int NowR,int l,int r,int now,int Add)//对于某个区间的数都乘上一个数
{
	if(NowL<=l&&NowR>=r)
	{
		ans[now]*=Add;
		Lazy[now]*=Add;
		lazy[now]*=Add;
		return;
	}
	Push_Down(now,l,r);
	PushDown(now,l,r);
	int mid=(l+r)/2;
	if(NowL<=mid)data(NowL,NowR,l,mid,Left(now),Add);
	if(NowR>mid)data(NowL,NowR,mid+1,r,Right(now),Add);
	PushUp(now);
}
int Query(int NowL,int NowR,int l,int r,int now)//区间查询总和
{
	int Return=0;
	if(NowL<=l&&NowR>=r)return ans[now];
	int mid=(l+r)/2;
	Push_Down(now,l,r);
	PushDown(now,l,r);
	if(NowL<=mid)Return+=Query(NowL,NowR,l,mid,Left(now));
	if(NowR>mid)Return+=Query(NowL,NowR,mid+1,r,Right(now));
	return Return;
}
int gcd(int a,int b)//最大公约数
{
	if(b==0)return a;
	return gcd(b,a%b);
}
int main()
{
	n=2;//一共两个数
	int A=read(),B=read();
	Build(1,1,n);//建树
	for(i=1;i<=n*4;i++)lazy[i]=1;//乘法懒标记的初值是1
	int Gcd=gcd(A,B);//计算出gcd(A,B)的值
	Data(1,1,1,n,1,A/Gcd);//对于1-1的区间都加上A/gcd(A,B)
	Data(2,2,1,n,1,B/Gcd);//对于2-2的区间都加上B/gcd(A,B)
	data(1,2,1,n,1,Gcd);//对于1-2的区间都乘上gcd(A,B)
	write(Query(1,2,1,n,1));//输出1-2这个区间的总和
}

这题其实就是P3373的升级版QAQ

再介绍一种比较cai的解决A+B问题的方法
旋转卡壳

这样一个优四个点组成的三角形中最长边为sqrt((a+b)2+0.32)可以发现就算a+b=0时0.32也可以忽略不计,自然只要用取整的方法计算就行了

但是,这样计算出来的值其实为abs(a+b)然而这与实际要计算的值不同
可以发现,当结果为负数时,b点一定在a点的右边,所以自然只要用叉积判断一下,十分的简单QAQ

#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cmath>
#include<string.h>
#include<algorithm>
using namespace std;
struct point//定义一个点的类
{double x,y;};
double sqr(double a)
{return a*a;}
int n,h,t,a,b,spj;
point Point[100001],check[100001];
bool cmp(point a,point b)
{
    if(a.y!=b.y)return a.y>b.y;
    return a.x>b.x;
}
double far(point a,point b)
{return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y));}
double dis(point a,point b)
{return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y));}
double side(point a,point b,point c)
{return a.x*b.y+a.y*c.x+b.x*c.y-a.x*c.y-a.y*b.x-b.y*c.x;}
double cha(point a,point b,point c)
{return(b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y);}
void Around(int n)//一个凸包模板
{
    h=n+1;
    t=n+1;
    int i;
    check[t++]=Point[1];
    for(i=1;i<=n;i++)
    {
        check[t]=Point[i];
        if(side(check[n],Point[i],Point[i+1]))break;
    }
    if(n<2)return;
    check[++t]=check[--h]=Point[++i];
    if(side(check[n+1],check[n+2],check[n+3])<0)swap(check[n+1],check[n+2]);
    for(++i;i<=n;i++)
    {
        if(side(check[h+1],check[h],Point[i])<0&&side(check[t-1],check[t],Point[i])>0)continue;
        while(t-h>1&&side(check[h+1],check[h],Point[i])>=0)++h;
        check[--h]=Point[i];
        while(t-h>1&&side(check[t-1],check[t],Point[i])<=0)--t;
        check[++t]=Point[i];
    }
}
void solve(int num)//一个旋转卡壳模板
{
    int max_y=-1e6,min_y=1e6,i,j;
    int max_y_id,min_y_id;
    for(i=1;i<=num;i++)
    {
        if(check[i].y>max_y)max_y=check[i].y,max_y_id=i;
        if(check[i].y<min_y)min_y=check[i].y,min_y_id=i;
    }
    double ans=dis(check[max_y_id],check[max_y_id]);
    check[num+1]=check[1];
    for(j=1;j<=num;j++,min_y_id=min_y_id%num+1)
    {
        while(cha(check[min_y_id+1],check[max_y_id+1],check[min_y_id])>cha(check[min_y_id+1],check[max_y_id],check[min_y_id]))max_y_id=max_y_id%num+1;
        ans=max(ans,dis(check[max_y_id],check[min_y_id]));
        ans=max(ans,dis(check[max_y_id],check[min_y_id+1]));
    }
    cout<<(long long)(ans)*spj;//输出
}
int main()
{
    scanf("%d%d",&a,&b);
    if(a==-b)//其实没有用的特判
    {
        printf("0");
        return 0;
    }
    n=4;
    Point[1].x=a;//放入四个点的坐标
    Point[1].y=0;
    Point[2].x=-b;
    Point[2].y=0;
    Point[3].x=a;
    Point[3].y=0.3;
    Point[4].x=a;
    Point[4].y=-0.3;
    spj=1;
    if(side(Point[3],Point[4],Point[2])>0)spj=-1;//叉积判断正负
    sort(Point+1,Point+n+1,cmp);//求出凸包要先排序
    Around(n);//再处理
    int i;
    for(i=h;i<t;i++)
    check[i-h+1]=check[i];//为了方便用旋转卡壳,先将数组放到开头
    solve(t-h);//计算直径
    return 0;
}

--------------------------华丽的分割线--------------------------------------

提供几种做A+B的方法

  • 1:做一个只有两个点的图(有点权),和一条把它们连在一起的边,根据点权跑一个最短路
  • 2:当做字符串读入,这样就不会炸了
  • 3:写个高精吧,两倍经验
  • 4:因为加法符合交换律,交换==翻转,自然就会先到平衡树了QAQ
  • 5:二分答案QAQ,这个没什么好讲的
  • 6:线段树都可以树状数组自然也不服QAQ
  • n:自动AC机

--------------------------华丽的分割线*2-----------------------------------
最后,祝大家A了这道题

发布了72 篇原创文章 · 获赞 22 · 访问量 5546

猜你喜欢

转载自blog.csdn.net/sxy__orz/article/details/87891071