hdu5908 Abelian Period 暴力 小小小小的优化

本文分享了一次编程竞赛的经历,针对一道题目从TLE到AC的优化过程,包括减少不必要的元素处理、避免重复初始化数组等技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接


这道题是昨天晚上(10.1)的 bestcoder #88 的1002题,一开始交的时候(小数据)过了,最后还是崩了...

想想算法没问题啊,从1到n/2枚举可能的k,对于 未在之前的过程中被确认为可以的 并且 整除n的k 去判断是否可以。

判断的过程就是首先在第一段即0-k-1的区间中记录下各个数字出现的次数,然后对于之后的每一个i*k-i*k+k-1去判断是否和第一段相符,一旦出现矛盾就跳出来。

对于任意一个符合条件的k,它的整数倍(如果也整除n的话)都是符合条件的,这一点是很显然的,那么标记一下,省去后面的判断(就类似于素数筛法里面做的那样)。

好像也想不到什么其他的好方法了,能够改动的都是一些细节部分啊......可是就是TLE了。


TLE代码如下:

//1002.cpp

#include <iostream>
#include <climits>
#include <cmath>
#include <cstring>
using namespace std;
#define maxn 100010
int a[maxn],tot[maxn],tott[maxn];
bool exist[maxn];
int main()
{
    int T,n,minn,maxx,i,j,k;
    bool flag;
    cin >> T;
    while (T--)
    {
        cin >> n;
        maxx = 0;
        minn = INT_MAX;
        for (i=0;i<n;++i)
        {
            cin >> a[i];
            maxx = max(maxx,a[i]);
            minn = min(minn,a[i]);
        }
        memset(exist,0,sizeof(exist));
        for (i=1;i<=n/2;++i)
        {
            if (exist[i] || n%i!=0)
                continue;
            memset(tot,0,sizeof(tot));
            memset(tott,0,sizeof(tott));
            for (j=0;j<i;j++)
                ++tot[a[j]];
            for (j=1;j<n/i;++j)
            {
                flag = true;
                for (k=minn;k<=maxx;++k)
                    tott[k] = tot[k];
                for (k=j*i;k<j*i+i;++k)
                    --tott[a[k]];
                for (k=minn;k<=maxx;++k)
                    if (tott[k]!=0)
                    {
                        flag = false;
                        break;
                    }
                if (!flag)    
                    break;
            }
            if (flag)
            {
                j = 1;
                while (i*j<=n)
                {
                    if (n%(i*j)==0)
                        exist[i*j] = true;
                    ++j;
                }
            }
        }
        for (i=1;i<n;++i)
            if (exist[i])
                cout << i << " ";
        cout << n << endl;
    }

    return 0;
}


那么既然这样的话就稍微改动一下细节吧。

上面的写法花了很多时间在无用元素的处理上(对本身就不存在的元素去判断是不是0)和每次判断一个k是否可行的时候都要做的两个memset。

既然这样的话就在这里处理一下吧,加一个数组去记录需要对哪些进行判断,需要对哪些最后清零。

然后这样就过了。

没错就是改进了这个地方然后就过了。

其实还有其他地方可以小小改进,比如说判断的k的范围,对每个数字出现的次数进行统计,可能分成的段数肯定小于等于它们的gcd;在这个基础上直接用总的个数/分的段数得到每个数字出现的次数,也就没有必要去单独拿出第一段统计了(其实也差不多...)


AC代码如下:

//1002.cpp

#include <iostream>
#include <climits>
#include <cmath>
#include <cstring>
using namespace std;
#define maxn 100010
int a[maxn],tot[maxn],tott[maxn],in[maxn];
bool exist[maxn];
int main()
{
    int T,n,minn,maxx,i,j,k,t,kk;
    bool flag;
    cin >> T;
    while (T--)
    {
        cin >> n;
        maxx = 0;
        minn = INT_MAX;
        for (i=0;i<n;++i)
            cin >> a[i];
        memset(exist,0,sizeof(exist));
        memset(tot,0,sizeof(tot));
        for (i=1;i<=n/2;++i)
        {
            if (exist[i] || n%i!=0)
                continue;
            t = 0;
            for (j=0;j<i;++j)
            {
                ++tot[a[j]];
                in[t++] = a[j];
            }
            for (j=1;j<n/i;++j)
            {
                flag = true;
                for (k=0;k<t;++k)
                    tott[in[k]] = tot[in[k]];
                for (k=j*i;k<j*i+i;++k)
                    --tott[a[k]];
                for (k=0;k<t;++k)
                    if (tott[in[k]]!=0)
                    {
                        for (kk=0;kk<t;++kk)
                            tot[in[kk]] = 0;
                        flag = false;
                        break;
                    }
                if (!flag)    
                    break;
            }
            if (flag)
            {
                j = 1;
                while (i*j<=n)
                {
                    if (n%(i*j)==0)
                        exist[i*j] = true;
                    ++j;
                }
            }
        }
        for (i=1;i<n;++i)
            if (exist[i])
                cout << i << " ";
        cout << n << endl;
    }

    return 0;
}


感觉有点小悲伤,就是因为这一点点的差别,直接导致了TLE,1002就没分了。

1001也是死在了细节上面,其中一步是

sum += (tot+1)*tot/2;

tot开的是int(因为tot的最大值是100000),sum开的是long long,以为可以类型转换过去的(蠢了...),然后就跪了。显然不可以啊,int*int再怎么也不会转化到long long去啊。

于是1001就在hack阶段给别人做出了贡献...明明是这么简单的题却就又没分了。

觉得这个错误纯粹是缺乏经验,再加上想当然了一下并且偷了个懒,只要把tot开成long long或者是写成 sum += (long long)(tot+1)*tot/2; 就可以了啊...

表示一下小心痛,以后会在这些地方多加注意的。

宝贵的经验教训啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值