zoukankan      html  css  js  c++  java
  • 6383. 【NOIP2019模拟2019.10.07】果实摘取

    题目

    题目大意

    给你一个由整点组成的矩形,坐标绝对值范围小于等于(n),你在((0,0)),一开始面向((1,0)),每次转到后面第(k)个你能看到的点,然后将这条线上的点全部标记删除。
    问最后一个被标记删除的点的坐标。


    正解

    先吐槽一句,原来删除的点是一条线上的,而不是一个点……
    害得我以为是一道神题……更可恨的是,我看不出我的暴力有什么错!

    既然一次删除的点是在一条线上的,那不妨将整条线上的东西看成一个点。
    那就变成了一个约瑟夫问题(也就是猴子选大王)。
    共有(8sum_{i=1}^{n}phi(i))个(原图分成(8)个三角形,减去对角线被算(4)次,加上垂直和水平方向(4)个)
    设现在有(n)个,则将第(k)个删除之后,就变成了(n-1)个的问题。所以可以通过递归来求。设(f_i)表示(i)个人在搞完之后最后一个留下的人是谁(编号从(0)开始)
    显然(f_i=(f_{i-1}+k)mod i)

    然而这个东西似乎会爆炸,因为总数是很多的。
    考虑如何快速计算这玩意儿。现在,最主要的瓶颈就是取模操作。
    由于不一定每次都会大于模数,所以考虑加几次(k)之后取一次模。
    假设现在有(n)个,设至少增加(x)个之后要取一次模。
    于是就有了不等式:(f_n+kxgeq n+x),解得(xgeq frac{n-f_n}{k-1})
    (当然不要忘了上取整)
    这样算会快很多,但是时间看起来似乎不是很好算。
    计算一下时间:当(nleq k)时,每次(n)只能变成(n+1),这一部分时间为(O(k)).
    (n>k)时,每次相当于加上(frac{n}{k})左右。
    尽管看起来并不是很靠谱,但实际上,某个(f_n+k)之后超过了(n+1),于是取模,(f_{n+1})不会超过(k)。所以当(n)远远大于(k)时,每次(f_n)(n)相差比较大,它们的差就可以近似地认为是(n)。(或者说约等于于加上(frac{n-k}{k}),有了个(-1)的常数,就省去了)
    加上(frac{n}{k})相当于乘(frac{k+1}{k})
    于是这一部分时间大概为(O(log_frac{k+1}{k}n))
    是对数级别时间复杂度,似乎很快的样子。如果用个换底公式,把(frac{1}{lgfrac{k+1}{k}})的常数给省掉,那么看起来是(O(lg n))的时间复杂度呢,似乎很优秀。但是这个常数实际上不能省!用计算器计算一下,这个常数大概为(230260)(这里的(lg)是指(log_{10}),不是(log_2),计算器里面没有直接提供(log_2)这种东西,所以干脆直接用(log_{10}了))。
    不过还好,时间还是过得去……

    接下来变成另一个问题:寻找排名第几项的位置。
    首先,找到这个位置在哪个象限。接下来只讨论第一象限的,其它的在此基础上旋转一下就好了。
    很容易想到二分。假设我们二分出了一个分数(frac{b}{a})
    现在我们要求再斜率为(frac{b}{a})这条直线一下的点数。
    具体来说,就是这个式子:(sum_{i=1}^{n}sum_{j=1}^{n}[gcd(i,j)=1]*[frac{j}{i}<frac{b}{a}])
    这个式子可以反演(说实在的,我对反演非常不熟悉)
    有个比较重要的性质:(sum_{d|n}mu(d)=[n=1])

    [sum_{i=1}^{n}sum_{j=1}^{n}sum_{d|gcd(i,j)}mu(d)*[frac{j}{i}<frac{b}{a}] \ =sum_{i=1}^nsum_{d|i}mu(d)sum_{d|j,jleq n}[j<frac{bi}{a}] \ =sum_{i=1}^nsum_{d|i}mu(d)min(lfloorfrac{n}{d} floor,lfloorfrac{bi}{ad} floor) \ =sum_{d=1}^nmu(d)sum_{i=1}^{lfloorfrac{n}{d} floor}min(lfloorfrac{n}{d} floor,lfloorfrac{bi}{a} floor)]

    有了这条式子,就可以在(O(nln n))的时间内判断了。
    至于如何二分,题解有种比较容易理解的暴力用小数来逼近分数的方法,Cold_Chair大爷有个(Stern-Brocot Tree)上二分的强大做法(由于节点的深度可能比较深,但拐角处是(lg)级别的,所以还要二分一下在某个方向上走多长距离。而且求答案的时候,还用到了整除分块)


    代码(未AC)

    最近没有AC的题目很多,代码都摆在这里,以后也不一定会去调试了……
    话说程序里我用的是小数来逼近分数的方法,不过为了追求常数,我把小数变成了分数的形式。当然,这个分数的分母都是(2)的幂。

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    #include <climits>
    #define N 100000
    #define ll long long
    int n,K;
    int p[N+10],np;
    bool inp[N+10];
    int phi[N+10],mu[N+10];
    ll calc(ll tar){
    //	return (K+calc(n-1))%n;
    	ll n=1,fn=0;
    	do{
    		ll x=(n-fn -1)/(K-1) +1;
    		if (tar<n+x)
    			return fn+K*(tar-n);
    		n=n+x;
    		fn=(fn+K*x)%n;
    	}
    	while (1);
    }
    ll below(ll a,ll b){
    	ll res=0;
    	for (int d=1;d<=n;++d){
    		ll s=0,n_d=n/d;
    		for (int i=1;i*d<=n;++i)
    			s+=min(b*i/a,n_d);
    		res+=s*mu[d];
    	}
    	return res;
    }
    int main(){
    //	freopen("in.txt","r",stdin);
    	freopen("garden.in","r",stdin);
    	freopen("garden.out","w",stdout);
    	scanf("%d%d",&n,&K);
    	if (K==1){
    		printf("%d %d
    ",n,-1);
    		return 0;
    	}
    	phi[1]=1,mu[1]=1;
    	for (int i=2;i<=n;++i){
    		if (!inp[i]){
    			p[++np]=i;
    			phi[i]=i-1;
    			mu[i]=-1;
    		}
    		for (int j=1;j<=np && i*p[j]<=n;++j){
    			inp[i*p[j]]=1;
    			if (i%p[j]==0){
    				phi[i*p[j]]=phi[i]*p[j];
    				break;
    			}
    			phi[i*p[j]]=phi[i]*(p[j]-1);
    			mu[i*p[j]]=mu[i]*mu[p[j]];
    		}
    	}
    	ll one=1,all=0;
    	for (int i=2;i<=n;++i)
    		one+=phi[i]*2;
    	all=one*4+4;
    	ll num=calc(all);
    	if (num==0)
    		printf("%d %d
    ",n,0);
    	else if (num==one+1)
    		printf("%d %d
    ",0,n);
    	else if (num==2*one+2)
    		printf("%d %d
    ",-n,0);
    	else if (num==3*one+3)
    		printf("%d %d
    ",0,-n);
    	else{
    		int rank=num%(one+1);
    		ll l=0,r=n,d=0;
    		while (d<30){
    			ll mid=l+r;
    			if (below(1<<d+1,mid)<rank)
    				l=mid,r<<=1;
    			else
    				r=mid,l<<=1;
    			d++;
    		}
    //		printf("%lf
    ",(double)r/(1ll<<d));
    		int x,y;
    		long double v=(long double)r/(1ll<<d),tmp=2e9;
    		for (ll i=n;i>=1;--i){
    			ll j=r*i/(1ll<<d)/*(ll)(v*i)*/;
    			if (v-double(j)/i<tmp){
    				tmp=v-double(j)/i;
    				y=j;
    				x=i;
    			}
    		}
    		if (num<one+1)
    			printf("%d %d
    ",x,y);	
    		else if (num<2*one+2)
    			printf("%d %d
    ",-y,x);
    		else if (num<3*one+3)
    			printf("%d %d
    ",-x,-y);
    		else
    			printf("%d %d
    ",y,-x);
    	}
    	return 0;
    }
    

    总结

    审题是关键。
    反演及其不熟练,需要找时间来提升。(实际上我似乎没有AC过一道反演的题目)

  • 相关阅读:
    HDU1720 A+B Coming
    HDU1390 ZOJ1383 Binary Numbers
    HDU1390 ZOJ1383 Binary Numbers
    HDU2504 又见GCD
    HDU2504 又见GCD
    HDU1335 POJ1546 UVA389 UVALive5306 ZOJ1334 Basically Speaking
    HDU1335 POJ1546 UVA389 UVALive5306 ZOJ1334 Basically Speaking
    HDU1020 ZOJ2478 Encoding
    HDU1020 ZOJ2478 Encoding
    HDU2097 Sky数
  • 原文地址:https://www.cnblogs.com/jz-597/p/11720582.html
Copyright © 2011-2022 走看看