bzoj 1025 //1025:[SCOI2009]游戏

bzoj 1025 //1025:[SCOI2009]游戏   //在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=1025

更多题解,详见https://blog.csdn.net/mrcrack/article/details/90228694BZOJ刷题记录

//1025:[SCOI2009]游戏
//在线测评地址https://www.luogu.org/problem/P4161
//输入3输出3解释如下
/*
1 2 3
1 2 3

1 2 3
1 3 2
1 2 3

1 2 3
2 1 3
1 2 3

1 2 3
2 3 1
3 1 2
1 2 3

1 2 3
3 1 2
2 3 1
1 2 3

1 2 3
3 2 1
1 2 3
*/
//对于所有可能的对应关系,有多少种可能的排数。n=3,有2,3,4排,3种情况.2019-9-28 19:15
//此文https://www.luogu.org/problemnew/solution/P4161?page=2 作者: FloatWinAya 更新时间: 2019-08-12 22:27思路不错,摘抄如下
/*
我仍记得,那天上午在jzoj上写这道题目,推了两个小时,也没推出来的恐惧

咳咳首先,我们分析一下样例的对应关系可以知道: 对于某个数,变换x轮变回原来的数,那么就在原序列中,就有相应x个数被用掉 比如,1->2->3->1;你可以把他们看作一个环。 那么整个序列就可以分解为若干个环。 但是这些环的长度不一样 然后我们可以发现 对于6个数的样例,1->2->3->1,4->5->4;6->6 也就是说,对于1、2、3,需要经过三轮变换才能变回原来的序列,对于4、5,经过两轮变换得到原来序列,对于6,经过一轮变换得到原来的序列。那么总共的序列变换次数,就是它们的最小公倍数(lcm) 那么原问题就转换为了:对于N,有x1+x2+x3+...xi=N,求N的不同自然数拆分的组合,再求每一个组合所有数的最小公倍数。

笔者添加部分,如下:
x1个数(环)+x2个数(环)+x3个数(环)+...xi个数(环)=N个数
n=3为例
3个数=1个数+1个数+1个数 1自环(1个数构成的环) 2自环(1个数构成的环) 3自环(1个数构成的环) 如1->1,2->2,3->3  LCM=1*1*1=1
3个数=1个数+2个数 自环+剩下2个数的环 如1->1,2->3->2 LCM=1*2=2
3个数=3个数 3个数的环 如1->2->3->1 LCM=3
一直以为是排列组合,弄了半天,与排列组合无关.2019-9-28 21:17

(下面是我赛后推出来的,参考dalao题解)

枚举N的所有加法拆分可能,这显然不可能,更何况最小公倍数还有重复。 那我们继续想这个最小公倍数是怎么来的。 显然我们不能枚举lcm,但是根据算数基本定理,最小公倍数就是这些加法拆分出来的数字质因数分解后每个指数的指数取max 那我们就可以尝试把lcm质因数分解, 问题就转换为求lcm质因数分解后这些数的和小于等于n的情况数量

这显然是一个背包问题

设f[i][j]表示选了前i个质数,和为j的情况数,初始值f[0][0]=1

枚举每个p的指数 状态转移方程显然 既然是背包问题,我们可以尝试将j倒序循环,控制f[i]只能由f[i-1]转移过来从而省去f的第一维

设f[j]表示这些指数的和小于等于j的情况数,因为小于j的情况可以用1补齐
*/
//此文https://blog.csdn.net/jiangshibiao/article/details/23040315思路不错,摘抄如下
/*
问题可以转换为已知一种正整数数字序列,它各项之和为N。
即求:对于A1+A2+...Ak=N,求LCM(A1,A2..Ak)的种数。
*/
//此文https://www.cnblogs.com/phile/p/4473192.html思路不错,摘抄如下
/*
我觉得很有思维含量的一道题
首先根据置换的知识,
每一个置换都可以表示成若干不想交的循环的乘积
所有循环的规模A1+A2+……AK=n
显然要求可能的排数,就是问n拆分成若干个数其可能的最小公倍数的个数
考虑到任意一个数大于1的自然数X一定能这样分解 令p为质数数组
X=p1^m1*p2^m2*…ph*mh
判断X是否可行我们可以想办法构造{Ah}使得其最小公倍数为X
显然{Ah}中pi的指数的最大值是mi
所以,和最小的满足最小公倍数为X的{Ah}是A1=p1^m1 A2=p2^m2 ……Ah=ph*mh(显然每个数都大于等于2)
如果满足A1+A2+……Ah<=n 则X为可行解
因为当A1+A2+……Ah=n 那显然存在可行解
而如果A1+A2+……Ah<n 我们一定可以令Ah+1~Ak全为1,这样最小公倍数不变,也满足了A1+A2+……AK=n
考虑到每一个X都对应唯一的分解质因式,每个分解质因式对应唯一的x
所以我们穷举上述的构造方案所得的最小公倍数一定不遗漏且唯一;
所以,寻找所有可行的X就转化为寻找满足下面条件的方案数
A1+A2+……Ah<=n
Ai=pi*mi (mi是不确定)
这样我们可用类似背包的方法解决
设f[i,j]表示用前i个质数,对应Ai和为j的方案数
显然f[i,j]=f[i-1,j](这个质数可以不用)+signma(f[i-1,j-p[i]^k]) (j-p[i]^k>=0) (穷举Ai的可能性);
初始f[0,0]=1;
最后答案就是signma(f[t,i]) 0<=i<=n;
*/
//此文https://lijinnn.cn/bzoj-1025-scoi2009%E6%B8%B8%E6%88%8F/代码写得不错.


线性筛+背包+二维数组

75ms / 8.50MB / 716B C++

//样例通过,提交AC.2019-9-30
#include <stdio.h>
#include <string.h>
#define LL long long
#define maxn 1010
LL f[maxn][maxn];
int n,not_prime[maxn],prime[maxn];
void linear_shaker(int x){
    int i,j;
    memset(not_prime,0,sizeof(not_prime)),prime[0]=0;
    for(i=2;i<=x;i++){
        if(!not_prime[i])prime[++prime[0]]=i;
        for(j=1;prime[j]*i<=x;j++){//漏了花括号,真叫没想法
            not_prime[prime[j]*i]=1;
            if(i%prime[j]==0)break;
        }
    }
}
int main(){
    int i,j,k;
    LL ans=0;
    scanf("%d",&n);
    linear_shaker(n);
    memset(f,0,sizeof(f)),f[0][0]=1;
    for(i=1;i<=prime[0];i++)
        for(j=0;j<=n;j++){
            f[i][j]+=f[i-1][j];//不放
            for(k=prime[i];k<=n;k*=prime[i])
                if(j>=k)f[i][j]+=f[i-1][j-k];//放
        }
    for(i=0;i<=n;i++)ans+=f[prime[0]][i];
    printf("%lld\n",ans);
    return 0;
}

线性筛+背包+一维数组

洛谷   29ms / 692.00KB / 657B C++

bzoj 1025   2019-10-24

836 kb36 msC++/Edit878 B

//样例通过,提交AC.2019-9-30
#include <stdio.h>
#include <string.h>
#define LL long long
#define maxn 1010
LL f[maxn];
int n,not_prime[maxn],prime[maxn];
void linear_shaker(int x){
    int i,j;
    memset(not_prime,0,sizeof(not_prime)),prime[0]=0;
    for(i=2;i<=x;i++){
        if(!not_prime[i])prime[++prime[0]]=i;
        for(j=1;prime[j]*i<=x;j++){//漏了花括号,真叫没想法
            not_prime[prime[j]*i]=1;
            if(i%prime[j]==0)break;
        }
    }
}
int main(){
    int i,j,k;
    LL ans=0;
    scanf("%d",&n);
    linear_shaker(n);
    memset(f,0,sizeof(f)),f[0]=1;
    for(i=1;i<=prime[0];i++)
        for(j=n;j>=0;j--)
            for(k=prime[i];k<=n;k*=prime[i])
                if(j>=k)f[j]+=f[j-k];//放
    for(i=0;i<=n;i++)ans+=f[i];
    printf("%lld\n",ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值