随机乱搞算法
模拟退火
bzoj3680 吊打XXX
题目大意:给定一些绳子和绳子上的重量,求出最后绳结的坐标。
思路:这个位置就是广义费马点,就是所有点到这个点的距离*每个点的权值最小的点。模拟退火,各种调常数。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<cstdlib> #define maxnode 10005 using namespace std; struct use{ double x,y,gi; }ai[maxnode]={0},ans; double minn=999999999999999LL; int n; double ran(){return rand()%1000/1000.0;} double fang(double a){return a*a;} double dis(use a,use b){return sqrt(fang(a.x-b.x)+fang(a.y-b.y));} double calc(use a) { int i;double j=0; for (i=1;i<=n;++i) j+=dis(a,ai[i])*ai[i].gi; if (j<minn){minn=j;ans=a;} return j; } void work(double t) { use a,now=ans;int i;double j; while(t>0.01) { a.x=now.x+t*(ran()*2-1); a.y=now.y+t*(ran()*2-1); j=calc(now)-calc(a); if (j>0||exp(j/t)>ran()) now=a; t*=0.980; } for (i=1;i<=1000;++i) { a.x=ans.x+t*(ran()*2-1); a.y=ans.y+t*(ran()*2-1); calc(a); } } int main() { int i,j;scanf("%d",&n);srand(23333); for (i=1;i<=n;++i) { scanf("%lf%lf%lf",&ai[i].x,&ai[i].y,&ai[i].gi); ans.x+=ai[i].x*1.0;ans.y+=ai[i].y*1.0; }ans.x/=n;ans.y/=n; work(500000);printf("%.3f %.3f\n",ans.x,ans.y); }
随机增量
bzoj2823 信号塔
题目大意:给定一些点,求最小的覆盖所有点的圆。
思路:假定当前的圆是ans,穷举所有点,如果某个点i不能被覆盖,就把当前点设为圆心、半径为0;然后找之前的点j,如果在圆外,就让i、j为直径做圆;然后找j之前的点k,如果在圆外就让i、j、k的三角形的外接圆为ans。这个复杂度的分析,据说期望o(n)。
求三角形外接圆:设三个点为a(x1,y1)b(x2,y2)c(x3,y3),圆心o(x,y),向量ao=(x-x1,y-y1)、bo=(x-x2,y-y2),这两个向量长度相等,就有(x-x1)^2+(y-y1)^2=(x-x2)^2+(y-y2)^2;同理由(x-x1)^2+(y-y1)^2=(x-x3)^2+(y-y3)^2。
化简后就是解一个二元一次方程组,如果设bx=x2-x1,by=y2-y1,cx=x3-x1,cy=y3-y1,d=2*(bx*cy-by*cx),那么x=(cy*(bx*bx+by*by)-by*(cx*cx+cy*cy))/d,y=(bx*(cx*cx+cy*cy)-cx*(bx*bx+by*by))/d.
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define eps 1e-8 #define maxm 500005 using namespace std; struct point{ double xi,yi; }ai[maxm]={0}; struct circle{ point c;double r; }; int cmp(double x,double y){ if (x-y>eps) return 1; if (x-y<-eps) return -1; return 0; } double sqr(double x){return x*x;} double dis(point x,point y){return sqrt(sqr(x.xi-y.xi)+sqr(x.yi-y.yi));} point center(point a,point b,point c){ double bx=b.xi-a.xi,by=b.yi-a.yi; double cx=c.xi-a.xi,cy=c.yi-a.yi; double d=2*(bx*cy-by*cx); double xx=(cy*(sqr(bx)+sqr(by))-by*(sqr(cx)+sqr(cy)))/d+a.xi; double yy=(bx*(sqr(cx)+sqr(cy))-cx*(sqr(bx)+sqr(by)))/d+a.yi; return (point){xx,yy}; } int main(){ int n,i,j,k;circle ans; scanf("%d",&n); for (i=1;i<=n;++i) scanf("%lf%lf",&ai[i].xi,&ai[i].yi); ans=(circle){ai[1],0.0}; for (i=1;i<=n;++i) if (cmp(dis(ai[i],ans.c),ans.r)>0){ ans.c=ai[i];ans.r=0; for (j=1;j<i;++j) if (cmp(dis(ai[j],ans.c),ans.r)>0){ ans.c=(point){(ai[i].xi+ai[j].xi)*0.5,(ai[i].yi+ai[j].yi)*0.5}; ans.r=dis(ai[i],ai[j])*0.5; for (k=1;k<j;++k) if (cmp(dis(ai[k],ans.c),ans.r)>0){ ans.c=center(ai[i],ai[j],ai[k]); ans.r=dis(ai[i],ans.c); } } }printf("%.2f %.2f %.2f\n",ans.c.xi,ans.c.yi,ans.r); }
pollard's pho
bzoj4522 密钥破解
题目大意:数论集合。
思路:先用pollard's pho分解N,用p和q算出r,求出e在r下逆元d,求c^d(mod N)。
注意:pollard‘s pho的时候可能返回0,这个时候要在用pollard’s pho做一遍(不能在返回的时候判断,那样可能死循环)。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> #define LL long long using namespace std; LL ab(LL x){return (x<0LL ? -x : x);} void gcd(LL a,LL b,LL &d,LL &x,LL &y){ if (!b){d=a;x=1LL;y=0LL;return;} gcd(b,a%b,d,y,x);y-=a/b*x;} LL mul(LL a,LL b,LL p){ LL c=0LL; for (;b;b>>=1LL){ if (b&1LL) c=(c+a)%p; a=(a+a)%p; }return c;} LL mi(LL a,LL b,LL p){ LL c=1LL; for (;b;b>>=1LL){ if (b&1LL) c=mul(c,a,p); a=mul(a,a,p); }return c;} LL pho(LL x,LL c){ LL i,k,y,x0,d,a,b; i=1LL;k=2LL; y=x0=(LL)rand()%x; while(true){ ++i;x0=(mul(x0,x0,x)+c)%x; gcd(ab(y-x0),x,d,a,b); if (d!=1LL&&d!=x) return d; if (i==k){y=x0;k+=k;} } } int main(){ LL e,nn,c,d,n,p=0LL,q,r; scanf("%I64d%I64d%I64d",&e,&nn,&c); while(!p) p=pho(nn,rand()%(nn-1LL)+1LL); q=nn/p;r=(p-1LL)*(q-1LL); gcd(e,r,p,d,q);d=(d%r+r)%r; n=mi(c,d,nn); printf("%I64d %I64d\n",d,n); }