HDU 4669 Mutiples on a circle (环形数列DP)byPlato

17 篇文章 0 订阅
HDU 4669 Mutiples on a circle (环形数列DP)byPlato
 
题意: 一个环形数列,可以把相邻的一段合并为一个数字。求其中有多少段能被k整除。(数列长度5W,k200)
 
题解的做法还没有看懂。。。下面是我自己的做法
先不考虑环的情况,线性数列下,很容易发现DP的做法:
f[i][j] -> f[i+1][ (j*len + a[i])%k ]
f[i][j] 是以第i个元素结尾,余数为j的方法数。
 
接下来考虑拆环,一般的拆环方法:双倍延长,分类讨论。
这里用的是分类讨论,
1. 1-n两个元素没有连在一起,这就是上面DP讨论的情况。
2. 1-n两个元素连在一起。也就是那段一定跨越1-n两个元素。
第2种情况朴素的做法是枚举两个指针O(n^2),然后采用一些预处理和区间查找值的技巧,可以转化在O(n*k)的复杂度内处理。具体看代码吧。
 
比赛的时候,把取模的运算规则搞错了,出了一些数据,但是都太弱了,WA到死。
(当时想当然的以为 ab%p = (a%p * ( len[b%p])%p +b%p)%p 正确的是ab%p = (a%p * ( len[b]%p) + b%p)%p   )
还用随机函数生成了一些数据,但都没有查出错误。一是出了数据手动验证正确性也很慢,二是数据的代表性不够。看来出数据也不一定是万能的,除非数据够强并且有验证的方法。
后面了解了下取模的运算规则,修改了下,在经过一些手残脑残的错误之后,终于AC了。
 
# include <cstdio >
# include <iostream >
# include <fstream >
# include <cstring >
# include <string >
# define OP(s) cout <<#s << "=" <<s << " ";
# define PP(s) cout <<#s << "=" <<s <<endl;
using namespace std;
typedef long long LL;
const int LN = 50010,LM = 210;

inline int getlen( int x)
{
     if (x < 10) return 10;
     if (x < 100) return 100;
     if (x < 1000) return 1000;
     return 10000;
}

int main()
{
    # ifndef ONLINE_JUDGE
        freopen( "test.txt", "r",stdin);
    # endif

     int n,k;
     while ( ~scanf( "%d %d", &n, &k))
    {
         static int a[LN],len[LN];
        memset(len, 0, sizeof(len));
         for ( int i = 1 ;i < = n;i ++)
        {
            scanf( "%d",a +i);
            len[i] = getlen(a[i]);
        }

        LL ans = 0;
         static LL f[ 2][LM];
         bool now = 1;
        memset(f, 0, sizeof(f));
         for ( int i = 2;i < = n;i ++)
        {
             for ( int j = 0;j < k;j ++) if (f[ !now][j])
            {
                f[now][(j *len[i] +a[i]) %k] += f[ !now][j];   ///! 手残1: (j*10 + a[i])%k
            }
            f[now][a[i] %k]   += 1;
            ans += f[now][ 0];

            now = !now;
            memset(f[now], 0, sizeof(f[now]));
        }

         static int seg1[LN],seg2[LN],num[LM],slen[LN];
        seg1[ 0] = 0;
        memset(num, 0, sizeof(num));
         int totlen = 1;
         for ( int i = 1;i < = n;i ++)
        {
            totlen = (totlen *len[i]) %k;
            slen[i] = totlen;
            seg1[i] = (seg1[i - 1] *len[i] +a[i]) %k;
             if (seg1[i] == 0) ++ans;   //if (seg1[i] == 0 && i != n) ++ans;
             for ( int j = 0;j < k;j ++)
                 if ( (j *(totlen %k) + seg1[i]) %k == 0)
                     ++num[j];
        }

        seg2[n] = a[n] %k; ///! 手残2:seg2[n+1] = 0;
        totlen = 1;
         for ( int i = n - 1;i > = 1;i --)
        {
            totlen = (totlen *len[i + 1]) %k;    ///!手残脑残3:totlen = a[n],totlen = (totlen*len[i])%k
            seg2[i] = (a[i] *totlen + seg2[i + 1]) %k;
        }

         for ( int i = n;i > 1;i --)
        {
             int x = seg2[i] %k;
             for ( int j = 0;j < k;j ++)
                 if ( (j *(slen[i] %k) + seg1[i]) %k == 0)
                     --num[j];
            ans += num[x];    ///! 手残脑残4: ans+= num[seg2[i]]
        }

        cout <<ans <<endl;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值