USACO2012 March Gold Large Banner

来源:互联网 发布:淘宝中文国际版app 编辑:程序博客网 时间:2024/06/03 02:53

Description

Bessie is returning from a long trip abroad to the Isle of Guernsey, and Farmer John wants to mount a nice “Welcome Home” banner for her arrival. Farmer John’s field has integer dimensions M x N (1 <= M, N <= 100,000), and he has installed a post at every possible point in the field with integer coordinates (if we assign a coordinate system to the field so that (0,0) is in the lower-left corner and (M,N) is in the upper-right corner). Of these (M+1) * (N+1) points, Farmer John must pick two as the endpoints of the banner.

Farmer John, being the perfectionist that he is, insists that the banner must be completely straight. This means that, for the two posts he chooses, there cannot be any other post on the line segment that the banner will form between them. Additionally, Farmer John wants the banner to have length at least L and at most H (1 <= L <= H <= 150,000). Farmer John needs your help to figure out how many possible ways he can hang the banner. The banner is reversible, so switching the two endpoints of the banner counts as the same way to hang the banner. As this number may bevery large, Farmer John simply wants to know what it is modulo B (1 <= B <=1,000,000,000).

题目大意

给定一个(N+1)(M+1)1NM100,000)的格点图,在任意两点之间连线构成线段,并满足条件:
1)线段长度在[L,R]1LR150,000)范围内;
2)线段中间不经过任何格点。
求不同的线段方案数(答案模B)。

Input

Line 1: Five space-separated integers: M, N, L, H and B.

Sample Input

2 2 1 3 100

Output

Line 1: One integer denoting the number of possible banners (modulo B).

Sample Output

28


挂Spylft大犇的讲解。


Solution

这样的题目一定要规划好枚举的方向,我们尝试先枚举两维。

因为这个图的左右是对称的,我们可以只考虑所有斜率>0的线段:根据这条,我们可以分出当线段与水平/竖直方向平行的情况来。显然,只有全部长度为1的线段满足要求,此时如果1在[L,R]内,则需要加上这些情况。

我们还可以算出一个格点图内与当前枚举的线段相同的线段个数:将这条线段视作与横竖直线构成了一个Rt,那么问题可以转化为求在格点图中与该Rt相同的个数,这个只需要O(1)即可完成(对于直角边分别为x,y的Rt,在纵方向上有(n-x+1)种可能性,在横方向上有(m-y+1)种可能性,根据乘法原理得到个数),所以重点是在枚举不同的Rt

通过分析两个条件,我们可以知道一个符合要求的Rt(设直角边分别为x,y)必须满足:

L2x2+y2R2gcd(x,y)=1
根据以上内容我们可以写出70分的O(n2)做法。

#include <cmath>#include <cstdio>int gcd(int a,int b){return b?gcd(b,a%b):a;}int n,m,L,R,P;void solve(){    int ans=0;    if(L<=1)ans=(1LL*(n+1)*m+1LL*(m+1)*n)%P;    for(int x=1;x<=n;x++)        for(int y=1;y<=m;y++){            double Len=sqrt(x*x+y*y);            if(Len<L||Len>R||gcd(x,y)!=1)continue;            ans=(ans+2LL*(n-x+1)*(m-y+1))%P;        }    printf("%d\n",ans);}int main(){    scanf("%d %d %d %d %d",&n,&m,&L,&R,&P);    if(P==1){puts("0");return 0;}    solve();}

那么显然只能枚举一维,另一维需要一些技术上的处理

对于一个x,所有符合题意的y必须满足gcd(x,y)=1,所以我们可以考虑从所有符合题意的y中减去gcd(x,y)1的情况。处理后面的部分需要一般化的容斥原理。

#include <cmath>#include <cstdio>#include <algorithm>#include <vector>#define M 100005using namespace std;int gcd(int a,int b){return b?gcd(b,a%b):a;}int n,m,L,R,P;int Eachprime[M][6],ptop[M];void init(int n){    for(int i=2;i<=n;i++)        if(!ptop[i])            for(int j=i;j<=n;j+=i)                Eachprime[j][ptop[j]++]=i;}long long sqr(int x){return 1LL*x*x;}long long calc(int low,int high,int prim){    long long lo=(low+prim-1)/prim;    long long hi=high/prim;    return ((m+1)*(hi-lo+1)-prim*(hi*(hi+1)-lo*(lo-1))/2)%P;}void solve(){    init(n);    int ans=0,mi=min(n,R);    int low=L,high=min(R,m);    for(int w=1;w<=mi;w++){        while(low-1&&sqr(low-1)+sqr(w)>=sqr(L))--low;        while(high&&sqr(high)+sqr(w)>sqr(R))--high;        if(low>high||low>m)continue;        int Allprime=1<<ptop[w];        int tmp=0;        for(int S=0;S<Allprime;S++){            int prim=1,p=1;            for(int k=0;k<(int)ptop[w];k++)                if(S&(1<<k))prim*=Eachprime[w][k],p=-p;            tmp=(tmp+p*calc(low,high,prim))%P;        }        ans=(ans+1LL*(n-w+1)*tmp)%P;        if(ans<0)ans+=P;    }    if(L<=1&&1<=R)ans=(2*ans+1LL*(n+1)*m+1LL*(m+1)*n)%P;    else ans=2*ans%P;    printf("%d\n",ans);}int main(){    scanf("%d %d %d %d %d",&n,&m,&L,&R,&P);    if(P==1){puts("0");return 0;}    solve();}

跟着上面的代码分析一下:

1) solve()函数中的low指针与high指针:当枚举的x逐渐变大时,这两个指针指向的就是当斜边长在[L,R]之间时的y[low,high]。low与high指针必须要>0(否则会出现平行竖直方向的情况),并且low>high,low与high都不超过m。由于指针是单调的,所以不用在内部重置low与high(这个会直接导致TLE)。

2) 容斥原理部分:假设x为30,那么它的质因子就为2,3,5,因子分别为2,3,5,6(2*3),10(2*5),15(3*5),30(2*3*5)。按照最基本的容斥原理(带三个圈的Venn图),我们要加上只含有一个、三个因子的元素数目,减去含有两个因子的元素数目,可以得到所有与x不互质的元素个数。如果因子数目一多,也可以按照一般化的容斥原理进行操作。那么init()就是在处理出每个数的质因子,复杂度接近O(nlog2n)。而Allprime部分则是在枚举因子的子集。来回跳动的p就是在判断是减去还是加上。

3) calc(int low,int high,int prim)函数部分:新处理出的lohi指针表示在[low,high]内第lo/hi个数含有prim因子。纵方向的相同情况计算可以看到已经在ans=(ans+1LL*(n-w+1)*tmp)%P处理掉了。而横方向的计算就有些麻烦了。
假设当前处理的因子为2,根据下图可以看出当前情况的个数为

i=lohi{m+1primi}=i=lohi(m+1)+i=1hi(primi)i=1lo1(primi)
根据等差数列公式就可以得到结果。
这里写图片描述 这里写图片描述

综上,本题总共的最坏复杂度是O(n26)

0 0
原创粉丝点击