codeforces 1080D Olya and magical square (思维+数学)(模拟)

传送门:codeforces 1080D

题意:给你一个 n 和 k ,表示你现在有一个边长为 2^n 的正方形,你需要对其进行恰好 k 次操作,使得:① 左下角和右上角的正方形边长一样,假设为 a;② 存在一条路径,这条路径连通左下角和右上角的正方形,并且这条路径上的所有正方形边长也都为 a。其中,操作指的是:找到目前图形中一个边长不为 1 的正方形,假设边长为 b,把它变成四个边长为 b/2 的小正方形。如果找不到这样一条路径,输出"NO";否则,输出"YES log2(a)"。如果路径上的边长 a 不唯一,随意输出一个。

tips:下述变量可能与题意中的变量无关。

思路:假设所选的路径均为"左下角->左上角->右上角",即沿着正方形的两条边走。那么我们的思路是先假设路径长度为2^(n-1)不断分割这条路径以外的正方形,直到除了这条路径以外没有正方形可以分割,缩短路径长度。接下来详细描述:

因为题目中给出的 k 是大于等于 1 的,所以最大的输出答案就是 n-1(即操作一次之后log2(2^(n-1))),如下图:

那么除去第一次操作(将初始正方形拆分成上图所示),我们接下来总的操作次数就是把右下角那个 2^(n-1) * 2^(n-1) 的正方形经过数次操作变为全都是边长为 1 的正方形,假设这个次数为 s。我们假设正方形长度为 x,那么经过分析得出右下角那块正方形的操作次数 s = 1 + 4 + ... + 4^(n-1) (此处原因读者可以尝试自行思考或借助打表,并不困难)。在这种情况下,显而易见的是,如果给定的 k <= 1 + s(此处+1是算上第一次操作),那么就可以直接输出"YES n-1"。

那如果 k > 1 + s 呢?我们可以把它拆分成下图所示:

那么除去前 5 次操作(将初始正方形拆分成上图所示),我们接下来总的操作次数就是把除了路径上的7个正方形外的 9 个 2^(n-2) * 2^(n-2) 的正方形经过数次操作变为全都是边长为 1 的正方形,假设这个次数为 k2。我们已经知道了一个如何将一个小正方形拆分了(但是与之前的公式有所区别),那么现在的问题是那个 5 次操作和 9 个正方形的 5 和 9 是怎么得到的。其实也很简单,如果明白了上一步的操作中 s 的计算方法的话,这里可以很轻松的想出,5 = 1 + 4,假设这一步操作的次数是 k1,那么 k1 = 1 + 4 + ... + 4^i,其中的 i 表示这次分割已经是第 i 次了。此时我们也可以得到 k2 = 1 + 4 + ... + 4^(n-i)。而那个 9 也不难想出,其实就是总的正方形个数减去路径上的正方形个数,我们假设这个个数为block。我们假设一条边被分为了 a 段(此处的 a 与题意中的 a 不同),那么可以得出总的正方形个数为 a * a ,路径上的正方形个数为 2 * a - 1,那么 block = a * a - 2 * a + 1也不难得出。那么此时的总结果 s = k1 + block * k2。如果 k <= s,即可输出"YES n-i",同时我们可以把上一步的 s 计算公式也归纳到这里面。(这些公式中只需要等比数列的计算就可以得到结果

虽然得到了计算公式,但这样并不足以解决问题,因为题目中的 n 的范围过于庞(e)大(xin),在计算 s 的过程中会爆long long,所以我们需要多进行一些思考。

通过等比数列求和公式我们可以知道:我们计算出的第一个结果为 s = 1 + ( 4^n - 1 ) / 3,其中最大的值为 4^n = 2^(2n)。题目中给出的 k 的最大值为 1e18,同时我们可以得到 2^62 为 4e18,即 n = 31 是我们计算的极限,而 n > 31 时,我们可以断言边长一定是2^(n-1),即输出"YES n-1"。

这样子还没有做完。读者读到这里可以自行尝试编写代码(应该有点感觉了),然后可以到下述代码中寻找坑点。

AC代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
ll fp(ll a,ll n){
    ll res=1;
    while(n){
        if(n&1) res=res*a;
        a=a*a;
        n>>=1;
    }
    return res;
}
int main(void){
    int t;scanf("%d",&t);
    while(t--){
        ll n,k;scanf("%lld%lld",&n,&k);
        if(n==2&&k==3) printf("NO\n");
        else if(n>31) printf("YES %lld\n",n-1ll);
        else{
            ll a=2;
            int flag=0;
            for(ll i=1;i<=n;i++){
                ll len=n-i;
                ll block=a*a-a*2ll+1ll;
                //printf("%lld\n",block);
                ll k1=(fp(4ll,i)-1ll)/3ll;
                ll k2=(fp(4ll,len)-1ll)/3ll;
                //printf("%lld %lld\n",k1,k2);
                if(k<=k1+k2*block){
                    flag=1;
                    printf("YES %lld\n",len);
                    break;
                }
                //printf("%lld\n",k1+k2*block);
                a*=2ll;
            }
            if(flag==0) printf("NO\n");
        }
    }
    return 0;
}

/*
29 96076792050570581
*/

tips:代码末尾的数据是 n = 29 时的极限数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值