HNOI2002《跳蚤》解题报告 - By LTL
ltl2011/06/05软件综合 IP:福建
这篇解题报告比较短,但是题目还是很好的,是一道数学性比较强的题目,对于数学不好的OIer可以学习一下。

题目:
Z城市居住着很多只跳蚤。在Z城市周六生活频道有一个娱乐节目。一只跳蚤将被请上一个高空钢丝的正中央。钢丝很长,可以看作是无限长。节目主持人会给该跳蚤发一张卡片。卡片上写有N+1个自然数。其中最后一个是M,而前N个数都不超过M,卡片上允许有相同的数字。跳蚤每次可以从卡片上任意选择一个自然数S,然后向左,或向右跳S个单位长度。而他最终的任务是跳到距离他左边一个单位长度的地方,并捡起位于那里的礼物。
比如当N=2,M=18时,持有卡片(10, 15, 18)的跳蚤,就可以完成任务:他可以先向左跳10个单位长度,然后再连向左跳3次,每次15个单位长度,最后再向右连跳3次,每次18个单位长度。而持有卡片(12, 15, 18)的跳蚤,则怎么也不可能跳到距他左边一个单位长度的地方。
当确定N和M后,显然一共有M^N张不同的卡片。现在的问题是,在这所有的卡片中,有多少张可以完成任务。

来源:
HNOI2002
POJ - P1091

题目简述:
求a[ i ]∈[1,m]且a[n]=m的数列使得存在{bn}满足∑a[ i ]b[ i ]=1的方案数。
n≤15,m≤100000000。

关键字:裴蜀定理,容斥原理

算法描述:
根据裴蜀定理,原问题转化为求满足gcd(a[ i ])=1的数列的方案数。
可以想到一种递推的方法:用f[ i ]表示当m=i时的答案,补集转化,则f[ i ]=i^n-f[k](k|i),这样一共有sqrt(m)个子问题,但是这样做的时间复杂度其实还是O(m)的,只不过除以了一个大常数,还是很容易TLE的。
那么我们换一个角度考虑。我们是要除去gcd≠1的部分,那么这其实是一个容斥问题。将m分解质因数,枚举gcd包含哪些质因子,做一遍容斥原理即可。
顺便说一下,听说这题答案有保证不要高精度,不过poj上没写,所以我还是写了高精度。

总结&收获:
这里递推的时间复杂度不能用调和级数的方法变成O(sqrt(m)logm)。刚开始我觉得直接枚举每个i的约数k是O(m)的,所以就想到枚举k的倍数,因为以前做一些题目的时候将枚举约数改成枚举倍数可以用调和级数的方法证明(n/1+n/2+...+n/n=nlogn)是O(nlogn)的,所以这次也想当然地以为变成了sqrt(m)logn,直到提交发现TLE了才反应过来我又sb了,于是才去思考容斥原理的算法。
总的来说,这道题目还是非常好的,可以练习一下OI方面的一些数学基础。同时也可以从中看出,湖南在02年就出这样的题目了,我的能力和强省还是有很大的差距的。

My Code(递推 - 会TLE):

/*
Title: PKU1091
Data: 2011-6-5
*/

#include <iostream>
#include <memory.h>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>

#define InputFileName        "XXXXXXX"
#define OutputFileName        "Data.out"

using namespace std;

const int HPLen = 15;

struct THP
{
    long long a[HPLen+1];
}f[30000], g[30000];
int n, m, a[30000], Total;

inline void operator *=(THP &a, const int b)
{
    for (int i = HPLen, r = 0; i; --i)
    {
        a.a[i] = a.a[i]*b+r;
        r = a.a[i]/1000000000;
        a.a[i] %= 1000000000;
    }
}

inline void operator +=(THP &a, const THP &b)
{
    for (int i = HPLen, r = 0; i; --i)
    {
        a.a[i] += b.a[i]+r;
        r = a.a[i]/1000000000;
        a.a[i] %= 1000000000;
    }
}

inline void operator -=(THP &a, const THP &b)
{
    for (int i = HPLen, r = 0; i; --i)
    {
        a.a[i] -= b.a[i]+r;
        if (a.a[i] < 0)
        {
            a.a[i] += 1000000000;
            r = 1;
        }
        else
            r = 0;
    }
}

void Print(const THP &x)
{
    int i = 1;
    for (; i < HPLen && ! x.a[i]; ++i);
    for (printf("%lld", x.a[i++]); i <= HPLen; printf("%09lld", x.a[i++]));
    printf("\n");
}

inline int Lower_Bound(const int k)
{
    int Res = 0;
    for (int l = 1, r = Total, mid = l+r >> 1; l <= r; mid = l+r >> 1)
        a[mid] >= k ? r = (Res = mid)-1 : l = mid+1;
    return Res;
}

int main()
{
    #ifndef ONLINE_JUDGE
    freopen(InputFileName, "r", stdin);
    freopen(OutputFileName, "w", stdout);
    #endif
    cin >> n >> m;
    f[1].a[HPLen] = 1;
    for (int i = 1; i*i <= m; ++i)
        if (m % i == 0)
        {
            a[++Total] = i;
            if (i*i < m)
                a[++Total] = m/i;
        }
    sort(a+1, a+Total+1);
    g[1].a[HPLen] = 0;
    for (int i = 1, j; i <= Total; ++i)
    {
        f[i].a[HPLen] = 1;
        for (j = 1; j <= n; ++j)
            f[i] *= a[i];
        f[i] -= g[i];
        for (j = a[i]*2; j <= m; j += a[i])
            if (m % j == 0)
                g[Lower_Bound(j)] += f[i];
    }
    Print(f[Lower_Bound(m)]);
    return 0;
}


My Code(容斥):

/*
Title: PKU1091
Data: 2011-6-5
*/

#include <iostream>
#include <memory.h>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>

#define InputFileName        "XXXXXXX"
#define OutputFileName        "Data.out"

using namespace std;

const int HPLen = 20;

struct THP
{
    long long a[HPLen+1];
}Ans, tmp[100], Minus;
int n, m, a[100], Total, Next[100], Prev[100];

inline void operator *=(THP &a, const int b)
{
    for (int i = HPLen, r = 0; i; --i)
    {
        a.a[i] = a.a[i]*b+r;
        r = a.a[i]/1000000000;
        a.a[i] %= 1000000000;
    }
}

inline void operator +=(THP &a, const THP &b)
{
    for (int i = HPLen, r = 0; i; --i)
    {
        a.a[i] += b.a[i]+r;
        r = a.a[i]/1000000000;
        a.a[i] %= 1000000000;
    }
}

inline void operator -=(THP &a, const THP &b)
{
    for (int i = HPLen, r = 0; i; --i)
    {
        a.a[i] -= b.a[i]+r;
        if (a.a[i] < 0)
        {
            a.a[i] += 1000000000;
            r = 1;
        }
        else
            r = 0;
    }
}

void Print(const THP &x)
{
    int i = 1;
    for (; i < HPLen && ! x.a[i]; ++i);
    for (printf("%lld", x.a[i++]); i <= HPLen; printf("%09lld", x.a[i++]));
    printf("\n");
}

void DFS(const int h, int s, int i)
{
    int j;
    for (; i <= Total; i = Next[i])
    {
        s *= a[i];
        memset(&tmp[h], 0, sizeof(THP));
        tmp[h].a[HPLen] = 1;
        for (j = 1; j <= n; ++j)
            tmp[h] *= m/s;
        if (h & 1)
            Minus += tmp[h];
        else
            Ans += tmp[h];
        Prev[Next[i]] = Prev[i];
        Next[Prev[i]] = Next[i];
        DFS(h+1, s, Next[i]);
        Prev[Next[i]] = Next[Prev[i]] = i;
        s /= a[i];
    }
}

int main()
{
    #ifndef ONLINE_JUDGE
    freopen(InputFileName, "r", stdin);
    freopen(OutputFileName, "w", stdout);
    #endif
    cin >> n >> m;
    int k = m;
    for (int i = 2; i*i <= k; ++i)
        if (k % i == 0)
        {
            a[++Total] = i;
            for (; k % i == 0; k /= i);
        }
    if (k > 1)
        a[++Total] = k;
    for (int i = 0; i <= Total; ++i)
        Prev[Next[i] = i+1] = i;
    Ans.a[HPLen] = 1;
    for (int i = 1; i <= n; ++i)
        Ans *= m;
    DFS(1, 1, 1);
    Ans -= Minus;
    Print(Ans);
    return 0;
}
来自:计算机科学 / 软件综合
2
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
ltl 作者
13年8个月前 IP:未同步
298447
这个[ i ]变成斜体太恶心了,怎么才能弄掉啊
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
上级专业
同级专业
ltl
学者 笔友
文章
50
回复
3650
学术分
2
2009/09/27注册,2个月12天前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:邮箱
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}