Spoj数论专题http://acm.hust.edu.cn:8080/judge/contest/view.action?cid=8035#overview
Problem A |
SPOJ ARCTAN |
Use of Function Arctan |
Problem B |
SPOJ CUTSQRS |
Cutting off Squares |
Problem C |
SPOJ FINFRAC |
Finding Fractions |
Problem D |
SPOJ GPINTRI |
Grid Points in a Triangle |
Problem E |
SPOJ PROOT |
Primitive Root |
Problem F |
SPOJ RNG |
Random Number Generator |
Problem G |
SPOJ EQU2 |
Yet Another Equation |
Problem H |
SPOJ BARB |
Barbarians |
Problem I |
SPOJ DIVSUM |
Divisor Summation |
Problem J |
SPOJ ETF |
Euler Totient Function |
Problem K |
SPOJ GCDEX |
GCD Extreme |
Problem L |
SPOJ INTEGER1 |
Power of Integer |
Problem M |
SPOJ KPEQU |
Equation |
Problem N |
SPOJ MOD |
Power Modulo Inverted |
Problem O |
SPOJ PAYING |
Paying in Byteland |
Problem P |
SPOJ PGCD |
Primes in GCD Table |
Problem Q |
SPOJ PRIME1 |
Prime Generator |
Problem R |
SPOJ ROBOT |
Robot Number M |
Problem S |
SPOJ TAN1 |
Tan and His Interesting Game |
Problem T |
SPOJ SSORT |
Silly Sort |
http://blog.csdn.net/wxfwxf328/article/details/7555054
#include<iostream> using namespace std; long long t,a,i; int main(){ cin>>t; while(t--){ cin>>a;i=2*a; while((i*i+1)%(i-a))i--; cout<<(i*i+1)/(i-a)<<endl; } return 0; }
原模型等价于在“1 1 1 1 1 1……”n个1中间的n-1个空格中选择任意个的不同选法的个数为2^(n-1)=c(n,0)+c(n,1)+c(n,2),其中win or lose的个数为2^(n-2)=c(n,0)+c(n,2)+……=c(n,1)+c(n,3)+…… tot=2^(x1-1)*2^(x2-1)*……=2^x
#include<iostream> #include<cmath> using namespace std; void dfs(int a,int b,int &x,bool &win) { if(a<b) swap(a,b);//a>b if(b==0){x=0,win=0;return;} dfs(b,a%b,x,win); x+=a/b-1;//x=sum{xi-1}; if(win==0||a/b>1) win=1; else win=0; } //len=log10(2^n)+1=n*log10(2)+1 int cal(int n)//2^n对应10进制长度 { if(n<=0) return 1; return floor(n*log10(2))+1; } int main() { int t;cin>>t; while(t--) { int a,b;cin>>a>>b; int x;bool win; dfs(a,b,x,win); cout<<(win?5:6)+cal(x)+cal(x-1)<<endl; } return 0; }
求满足a/b < p/q < c/d 的p,q且q最小。
解法:连分数(自己baidu)
#include<iostream> using namespace std; typedef long long ll; ll dfs(ll a,ll b,ll c,ll d) { ll k=a/b; a-=b*k;c-=d*k; if(c>d) return 1; if(a==0) return d/c+1; return d*dfs(d,c,b,a)/c+1; } int main() { ll a,b,c,d,y; while(scanf("%lld%lld%lld%lld",&a,&b,&c,&d)!=EOF) { y=dfs(a,b,c,d); printf("%lld/%lld\n",a*y/b+1,y); } return 0; }
求满足 y <= ax / b and x <= n的整点个数。
cd=ad%bd;ans(ade)=ans(cde)+ad/bd*ans(bde)
ans(bde)可以直接求出
#include<cstdio> #include<iostream> using namespace std; typedef long long ll; int gcd(int a,int b) { return b?gcd(b,a%b):a; } ll dfs(ll n,ll a,ll b)//RT三角 格点数 { ll t=n*(n+1)/2*(a/b); a%=b; if(a==0) return n+1+t; ll d=a*n/b; t+=(n+1)*(d+1)+d/a+1;//+平行四边形 return t-dfs(d,b,a);//-三角形(与RT三角形等价) } int main() { int t;cin>>t; while(t--){ int n,a,b; scanf("%d%d%d",&n,&a,&b); int p=gcd(a,b); printf("%lld\n",dfs(n,a/p,b/p)); } return 0; }
根据题目要求的来判断,直接暴力求出所有因子
#include<iostream> #include<cstdio> #include<cmath> using namespace std; typedef long long ll; ll modPow(ll a,int n,int p) { ll res=1; while(n) { if(n&1) res=res*a%p; n>>=1;a=a*a%p; } return res; } int fac[100000],cnt; int main() { int p,n; while(scanf("%d%d",&p,&n),n||p) { cnt=0;p--; int lim=sqrt(p+1); for(int i=2;i<=lim;i++) if(p%i==0) fac[cnt++]=i,fac[cnt++]=p/i; while(n--) { int x; scanf("%d",&x); bool fg=1; for(int i=0;i<cnt&&fg;i++) if(modPow(x,fac[i],p+1)==1) fg=0; puts(fg?"YES":"NO"); } } return 0; }
#include<iostream> #include<iomanip> using namespace std; //根据容斥 //ans=f(x,y,z)-f(-x,y,z)-f(x,-y,z)-f(x,y,-z)+f(-x,-y,z)+f(-x,y,-z)+f(x,-y,-z)-f(-x,-y,-z) //f(i,j,k)为小于sum,且x,y,z在区间[-(x+y+z),sum]的情况 仅在f(i,j,k)>0时,有意义 //n维f(i,j,k)=s^n/(n!);二维s*s/2(面积) 三维s*s*s/6(体积) //ans=ans/tot int t,n,a,b,x[11]; double ans; void dfs(int i,int sum,int k) { if(i==n) { if(sum>0) { double x=1; for(int i=0;i<n;i++) x=x*sum/(i+1); ans+=k*x; } return; } dfs(i+1,sum+x[i], k); dfs(i+1,sum-x[i],-k); } double cal(int s) { ans=0; dfs(0,s,1); return ans; } int main() { cin>>t; while(t--) { cin>>n>>a>>b; for(int i=0;i<n;i++) cin>>x[i]; double tot=1; for(int i=0;i<n;i++) tot*=2*x[i]; cout<<fixed<<setprecision(9)<<(cal(b)-cal(a))/tot<<endl; } return 0; }
Pell方程求最小解,此题需要高精度
解法:http://hi.baidu.com/toremote/item/d3b81ccee2b4330d0bd93a2e
证明:http://mathworld.wolfram.com/PellEquation.html
import java.math.BigInteger; import java.util.Scanner; //a[n]=(g[n]+a[0])/h[n] //g[n]=a[n-1]*h[n-1]-g[n-1] //h[n]=(N-g[n]*g[n])/h[n-1] //p[n]=a[n-1]*p[n-1]+p[n-2] //q[n]=a[n-1]*q[n-1]+q[n-2] //so: //p[n]*q[n-1]-p[n-1]*q[n]=(-1)^(n+1); //p[n]^2-N*q[n]^2=(-1)^(n+1)*h[n+1]; public class Main { public static BigInteger p, q; public static void solve(int n) { BigInteger N, p1, p2, q1, q2, a0, a1, a2, g1, g2, h1, h2; g1 = q2 = p1 = BigInteger.ZERO; h1 = q1 = p2 = BigInteger.ONE; a0 = a1 = BigInteger.valueOf((long)Math.sqrt(1.0*n)); N = BigInteger.valueOf(n); while (true) { g2 = a1.multiply(h1).subtract(g1); //g2=a1*h1-g1 h2 = N.subtract(g2.pow(2)).divide(h1); //h2=(n-g2^2)/h1 a2 = g2.add(a0).divide(h2); //a2=(g2+a0)/h2 p = a1.multiply(p2).add(p1); //p=a1*p2+p1 q = a1.multiply(q2).add(q1); //q=a1*q2+q1 if (p.pow(2).subtract(N.multiply(q.pow(2))).compareTo(BigInteger.ONE) == 0) return;//p^2-n*q^2=1 g1 = g2;h1 = h2;a1 = a2; p1 = p2;p2 = p; q1 = q2;q2 = q; } } public static void main(String[] args) { Scanner cin = new Scanner(System.in); int t=cin.nextInt(); while (t--!=0) { solve(cin.nextInt()); System.out.println(p + " " + q); } } }
此题时限有点紧
从max(c[i])开始枚举每一个值,对任意i,j解同余方程判断,复杂度10*10*10^6*O(ext_gcd)
#include<algorithm> #include<iostream> #include<cstdio> #include<set> using namespace std; int c[22],p[22],l[22]; int n,t,d; int ex_eculid(int a,int b,int &x,int &y) { if(b==0){x=1;y=0;return a;} d=ex_eculid(b,a%b,x,y); t=x;x=y;y=t-(a/b)*y; return d; } bool judge(int k) { int x,y,a,b,d,m; for(int i=0; i<n; i++) for(int j=0; j<i; j++) { a=p[i]-p[j]; b=c[j]-c[i]; //if(a<0) a=-a,b=-b; d=ex_eculid(a,k,x,y); if(b%d) continue; m=k/d; x=(x*b/d%m+m)%m; if(x<=l[i]&&x<=l[j]) return 0; } return 1; } int main() { int t; cin>>t; while(t--) { cin>>n; for(int i=0; i<n; i++) scanf("%d%d%d",c+i,p+i,l+i); for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(p[i]>p[j]) swap(p[i],p[j]),swap(c[i],c[j]),swap(l[i],l[j]); int k=max(n,*max_element(c,c+n)); while(!judge(k)) k++; cout<<k<<endl; } return 0; }
除了J题最水的一题,筛法求解
#include<iostream> using namespace std; const int maxn=500000; int a[maxn+10]; int main() { for(int i=1;i<=maxn/2;i++) for(int x=i+i;x<maxn;x+=i) a[x]+=i; int x,t; cin>>t; while(t--) { scanf("%d",&x); printf("%d\n",a[x]); } return 0; }
#include<iostream> using namespace std; const int maxn=1000001; int phi[maxn]; int main() { for(int i=0;i<maxn;i++) phi[i]=i; for(int i=2;i<maxn;i+=2) phi[i]/=2; for(int i=3;i<maxn;i+=2) if(phi[i]==i) for(int j=i;j<maxn;j+=i) phi[j]-=phi[j]/i; int t; cin>>t; while(t--) { int x; cin>>x; cout<<phi[x]<<endl; } return 0; }
题目描述有问题,spoj讨论里有。更正题目:http://uva.onlinejudge.org/external/114/11426.pdf(n范围是1000000,其它相同)
把关系:a[x]=sum(a[i]*j){i*j=x}搞定即可
#include<iostream> using namespace std; typedef long long ll; const int maxn=1000010; int e[maxn];//euler ll a[maxn];//ans //a[x]=sum(a[i]*j) {i*j=x} int main() { for(int i=1;i<maxn;i++) e[i]=i; for(int i=2;i<maxn;i+=2) e[i]/=2; for(int i=3;i<maxn;i+=2) if(e[i]==i) for(int j=i;j<maxn;j+=i) e[j]-=e[j]/i; copy(e+2,e+maxn,a+2); for(int i=2;i<=1000;i++) { a[i*i]+=e[i]*i; for(int j=i*i+i,k=i+1;j<maxn;j+=i,k++) a[j]+=e[i]*k+e[k]*i; } for(int i=1;i<maxn;i++) a[i]+=a[i-1]; for(int n;scanf("%d",&n),n;printf("%lld\n",a[n])); return 0; }
比较坑的一题,有个溢出的情况要注意:lim(a,i)=max{k|k^i<=a},会有可能出现类似ll(2^60)>ll(3^60)的情况,为此wa了无数次。
从2次方开始,每次的次方数k对应sig要减去其所有因子的sig值。
#include<iostream> #include<iomanip> #include<cmath> #include<cstdio> using namespace std; typedef long long ll; int sig[100]; ll ppow(ll a,ll n) { ll b=1; while(n) { if(n&1) b=b*a; n>>=1;a=a*a; } return b; } ll lim(ll a,int i)//return max{k|k^i<=a} { ll k=pow(a,1.0/i)-1; while(1) { if(pow(k*1.0,i*1.0)>1e18||ppow(k,i)>a) return k-1; k++; } } ll cal(ll a) { ll ans=a; for(int i=2;;i++) { ll k=lim(a,i); if(k==1) return ans; ans+=(k-1)*sig[i]; } } int main() { for(int i=1;i<100;i++) sig[i]=i; for(int i=1;i<100;i++) for(int j=1;j<i;j++) if(i%j==0) sig[i]-=sig[j]; ll a,b; while(cin>>a>>b,a||b) cout<<cal(b)-cal(a-1)<<endl; return 0; }
1/N! = 1/X + 1/Y 的解个数,对n!分解处理,需答案高精度。
//n!=p1^q1*p2^q2*... //ans=(2*q1+1)*(2*q2+1)*... //n!=x*y/(x+y) x中因子pi个数有2*qi+1个,对应y中pi因子个数唯一对应 #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int maxn=10010; int prm[maxn],cnt=0; bool isprm[maxn]; struct bint { int ln,v[260]; bint(int r=0){ memset(v,0,sizeof(v)); for(ln=0;r>0;r/=10000) v[ln++]=r%10000; } bint operator * (const bint &b)const { bint res;res.ln=0; if(0==b.ln){res.v[0]=0;return res;} for(int i=0;i<ln;i++) { for(int j=0,cy=0;j<b.ln||cy>0;j++,cy/=10000) { if(j<b.ln) cy+=v[i]*b.v[j]; if(i+j<res.ln) cy+=res.v[i+j]; if(i+j>=res.ln) res.v[res.ln++]=cy%10000; else res.v[i+j]=cy%10000; } } return res; } void write() { printf("%d",ln==0?0:v[ln-1]); for(int i=ln-2;i>=0;i--) printf("%04d",v[i]); puts(""); } }; ll cal(int p,int n) { ll ans=0; while(n) { n/=p; ans+=n; } return ans; } int main() { memset(isprm,1,sizeof(isprm));cnt=0; for(int i=2;i<maxn;i++) if(isprm[i]) { prm[cnt++]=i; for(int j=i*i;j<maxn;j+=i) isprm[j]=0; } int n; while(cin>>n,n) { bint ans=1; for(int i=0;prm[i]<=n;i++) { bint tmp(cal(prm[i],n)*2+1); ans=ans*tmp; } ans.write(); } return 0; }
离散对数 k%z = xy%z ,大致方法为枚举1~sqrt(z),求出x^(i)%z,则ans=(i*sqrt(z)+j),然后枚举i,二分或者Hash找对应的j。这一题lp模板不可以用,因为不满足x与z互素。
#include<iostream> #include<cstdio> #include<cmath> using namespace std; typedef long long ll; const int maxn=65535; struct Hash { int a,b,nxt; }hash[maxn<<1]; int flg[maxn]; int top,idx; void ins(int a,int b) { int k=b&maxn; if(flg[k]!=idx) { flg[k]=idx; hash[k].nxt=-1; hash[k].a=a; hash[k].b=b; return; } while(hash[k].nxt!=-1) { if(hash[k].b==b) return ; k=hash[k].nxt; } hash[k].nxt=++top; hash[top].nxt=-1; hash[top].a=a; hash[top].b=b; } int find(int b) { int k=b&maxn; if(flg[k]!=idx) return -1; while(k!=-1) { if(hash[k].b==b) return hash[k].a; k=hash[k].nxt; } return -1; } int gcd(int a,int b){return b?gcd(b,a%b):a;} int ext_gcd(int a,int b,int &x,int &y) { int t,ret; if(!b) {x=1,y=0;return a;} ret=ext_gcd(b,a%b,x,y); t=x,x=y,y=t-a/b*y; return ret; } int inv(int a,int b,int n) { int x,y,e; ext_gcd(a,n,x,y); e=(ll)x*b%n; return e<0?e+n:e; } int pow_mod(ll a,int b,int c) { ll ret=1%c;a%=c; while(b) { if(b&1) ret=ret*a%c; a=a*a%c;b>>=1; } return ret; } int baby(int A,int B,int C) { top=maxn;++idx; ll buf=1%C,D=buf,k; int i,d=0,tmp; for(i=0;i<=100;buf=buf*A%C,i++) if(buf==B) return i; while((tmp=gcd(A,C))!=1) { if(B%tmp)return -1; ++d; C/=tmp;B/=tmp; D=D*A/tmp%C; } int M=(int)ceil(sqrt((double)C)); for(buf=1%C,i=0;i<=M;buf=buf*A%C,i++) ins(i,buf); for(i=0,k=pow_mod((ll)A,M,C);i<=M;D=D*k%C,i++) { tmp=inv((int)D,B,C); int w; if(tmp>0&&(w=find(tmp))!=-1) return i*M+w+d; } return -1; } int main() { int a,b,c; while(scanf("%d%d%d",&a,&c,&b),a||b||c) { b%=c; int ans=baby(a,b,c); if(ans<0) puts("No Solution"); else printf("%d\n",ans); } return 0; }
每个硬币值为2^i,求最少用多少硬币表示n。
N=a*1+b*2+c*4+d*8+e*16+……{a,b,c,d,e取值为0或1},答案即为此数对应2进制中1的个数。
#include<iostream> using namespace std; struct bint { int ln,v[300]; bint(int r=0){ memset(v,0,sizeof(v)); for(ln=0;r>0;r/=10000) v[ln++]=r%10000; } bool operator < (const bint &b)const { int i;if(ln!=b.ln) return ln<b.ln; for(i=ln-1;i>=0&&v[i]==b.v[i];i--); return i<0?0:v[i]<b.v[i]; } bint operator - (const bint &b)const { bint res;int i,cy=0; for(res.ln=ln,i=0;i<res.ln;i++) { res.v[i]=v[i]-cy; if(i<b.ln) res.v[i]-=b.v[i]; if(res.v[i]<0) cy=1,res.v[i]+=10000; else cy=0; } while(res.ln>0&&res.v[res.ln-1]==0) res.ln--; return res; } bint operator * (const bint &b)const { bint res;res.ln=0; if(0==b.ln){res.v[0]=0;return res;} for(int i=0;i<ln;i++) { for(int j=0,cy=0;j<b.ln||cy>0;j++,cy/=10000) { if(j<b.ln) cy+=v[i]*b.v[j]; if(i+j<res.ln) cy+=res.v[i+j]; if(i+j>=res.ln) res.v[res.ln++]=cy%10000; else res.v[i+j]=cy%10000; } } return res; } bool read(char *buf) { int w,u,lnn=strlen(buf); memset(v,0,sizeof(v)); if(48==buf[0]&&0==buf[1]) return 1; for(w=1,u=0;lnn;) { u+=(buf[--lnn]-48)*w; if(w*10==10000) v[ln++]=u,u=0,w=1; else w*=10; } if(w!=1) v[ln++]=u; return 1; } void write() { printf("%d",ln==0?0:v[ln-1]); for(int i=ln-2;i>=0;i--) printf("%04d",v[i]); puts(""); } }a[5000]; char s[1111]; int main() { a[0]=1; for(int i=1;i<3500;i++) a[i]=a[i-1]*2; bint n; int t; cin>>t;getchar(); while(t--) { gets(s); n.read(s); int i=0,ans=0; bint zero(0); while(zero<n) { if(n<a[i]) break; n=n-a[i]; i++; ans++; } while(zero<n) { while(n<a[i]) i--; n=n-a[i]; ans++; } cout<<ans<<endl; } }
所有题目中时限最长的一道,但还有卡时间的。
//参考自前人代码,表示压力山大 //ans(x,y)=ans(min(x,y),min(x,y)) #include<iostream> using namespace std; const int maxn=10000010; int a[maxn];//质因子个数 int b[maxn];//出现次数为2的质因子的个数(仅需要处理出0、1、其它) int c[maxn];//c[i]为gcd(x,y)<=i的总个数 void init() { for(int i=2; i<maxn; i++) if(!a[i]) for(int j=1,k=i; k<maxn; j++,k+=i) { a[k]++; if(j%i==0)//b[k]=i^3*xx { if((j/i)%i==0) b[k]=2; else b[k]++; } } for(int i=2;i<maxn;i++) { //容斥处理出c数组 if(b[i]==-1||b[i]>1) c[i]=0; else if(b[i]==1) c[i]=a[i]&1?-1:1; else c[i]=a[i]&1?a[i]:-a[i]; c[i]+=c[i-1];//将c数组处理为前n个之和 } } int main() { init(); int t;cin>>t; while(t--) { int a,b,m,aa,bb; cin>>a>>b; m=min(a,b); long long ans=0; for(int i=2;i<=m;i++) { aa=a/i;bb=b/i; int t=min(a/aa,b/bb)-i;//a/aa:使a/i相同的i的个数 ans+=(long long)(c[i+t]-c[i-1])*aa*bb; i+=t;//直接跳到下一个不同i // ans+=(c[i]-c[i-1])*(a/i)*(b/i);直接这样计算可出答案,但会超时 } cout<<ans<<endl; } return 0; }
另一道水题,打个区间素数表
#include<iostream> using namespace std; int t; const int maxn=120000; bool isprm[maxn]; int prm[maxn],cnt=0; int vis[maxn*3]; int main() { memset(isprm,1,sizeof(isprm)); for(int i=2;i<maxn;i++) if(isprm[i]) { prm[cnt++]=i; if(i>40000) continue; for(int j=i*i;j<maxn;j+=i) isprm[j]=0; } int t,m,n; cin>>t; while(t--) { cin>>m>>n; memset(isprm,1,n-m+1); for(int i=0;prm[i]*prm[i];i++) for(int j=m/prm[i];j*prm[i]<=n;j++) if(j>1&&j*prm[i]>=m) isprm[j*prm[i]-m]=0; for(int i=m;i<=n;i++) if(i>1&&isprm[i-m]) printf("%d\n",i); if(t) cout<<endl; } }
比较简单题,主要就是所有约数的欧拉数之和等于它本身,由(黑书229)性质可以容易得出
N=p^x,M=q^y
Sum(euler(pi))=1+(p-1)*(1+p+p^2+……+p^(x-1))=p^x
Sum(euler(qi))=1+(p-1)*(1+q+q^2+……+q^(y-1))=q^y
因此:Sum(euler(pi))*Sum(euler(qi))=p^x*q^y=M*N,得证
#include<iostream> #include<cstring> #include<cstdio> using namespace std; //sum(euler(i))=x {gcd(i,M)=i} //a+b+c+1=sum(euler(i))=x const int mod=10000; int p[1111],a[1111],dp[1111]; int modpow(int a,int n) { int ans=1; while(n) { if(n&1) ans=ans*a%mod; a=a*a%mod;n>>=1; } return ans; } int main() { int t; cin>>t; while(t--) { int n; cin>>n; for(int i=1;i<=n;i++) scanf("%d%d",p+i,a+i); int m=1; for(int i=1;i<=n;i++) m=m*modpow(p[i],a[i])%mod; int s=p[1]==2?2:1; memset(dp,0,sizeof(dp));dp[0]=1; for(int i=(p[1]==2?2:1);i<=n;i++) for(int j=n;j>0;j--) dp[j]=(dp[j]+dp[j-1]*(p[i]-1))%mod; int x=0,y=0; for(int i=1;i<=n;i++) i&1?x+=dp[i]:y+=dp[i]; x%=mod;y%=mod; cout<<y<<endl<<x<<endl<<((m-x-y-1)%mod+mod)%mod<<endl; } return 0; }
20题里唯一没过的一题。
//黑书248 #include<iostream> using namespace std; int a[1<<10],p[1<<10]; int main() { int n,cas=1; while(cin>>n,n) { for(int i=0;i<n;i++) { scanf("%d",a+i); p[a[i]]=i; } sort(a,a+n); int ans=0; for(int i=0;i<n;i++) { int k=0,&j=p[a[i]]; while(j!=i) { ans+=a[j];k++; swap(p[a[i]],p[a[j]]); } ans+=min(a[i]*k,a[i]*2+a[0]*(k+2)); } cout<<"Case "<<cas++<<": "<<ans<<endl<<endl; } return 0; }