【BZOJ2118】墨墨的等式【循环节做法】

取最小的ai,记作m。如果x能被凑出来,那么x+m就也能被凑出来。

根据这个思路,取数组$DP(0..m-1)$,表示能构成的最小数,使得$DP(i)\%m=0$,使用的是除开m的其他数

那么dp转移就是:$DP[(x+v)\%m] \leftarrow DP(x)+v$,很像最短路的松弛操作

所以我们把0..m-1看成图节点,建边后跑最短路即可  

大多数做法就直接跑spfa了,其实有更好的方法

对于一个数v,考虑它贡献的边

  • 从点 x出发, 有边
    • (x+v, x)
  • 从点 x+v出发, 有边
    • (x+2v, x+v)
  • 从点 x+2v出发, 有边
    • (x+3v, x+v)  

事实上,根据数论我们知道, (x + kv) % m是一个循环节只会遍经m / gcd(m,v)个点。这些点看作一个环的话,共有gcd(M,v)个环

如果沿着环上的边进行转移,效率能提高不少。然而这样是可以的,也就是把边集划分后,分别进行最短路算法(dijkstra)。

Let h = gcd(M,v)
for i = 0 to h-1
    Let S = {}
    for j = 0 to M/h
        S union ( (i + v*j) % M, (i + v*(j+1)) % M )
        /* notation: ( tail, head ) */
    Find the vertex y with shortest distance from 0
        from the set of vertices in the cycle S
    consider edges e in cycle S, starting from vertex y
        relax( DP(e.head), DP(e.tail) + v )

上面步骤的复杂度是 O(M),因此总的复杂度是 O(M*N)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=12+3;
const int M=5e5+3;

typedef long long ll;
const ll INF=0x3f3f3f3f3f3f3f3f;

int n,m,a[N];

ll d[M];

ll solve(ll x)
{
    ll ret=0;
    for(int i=0;i<m;i++)
        if(d[i]<=x) 
            ret+=(x-d[i])/m+1;
    return ret;
}
ll Bmn,Bmx;

int gcd(int a,int b) {
    return b==0?a:gcd(b,a%b);
}

int main()
{
    scanf("%d%lld%lld",&n,&Bmn,&Bmx);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    int t=1;
    while(a[t]==0)t++;
    m=a[t];
    
    memset(d,0x3f,sizeof d);
    d[0]=0;
    
    for(t++;t<=n;t++)
    {
        int v=a[t];
        int h=gcd(m,v);
        for(int i=0;i<h;i++)
        {
            int now=i,st=-1;
            while(1)
            {
                if(st==-1 || d[st]>d[now])
                    st=now;
                now=(now+v)%m;
                if(now==i)break;
            }
            if(st==-1 || d[st]==INF)continue;
            now=st;
            ll val=d[st];
            do
            {
                d[now]= val= min(val,d[now]);
                now=(now+v)%m;
                val+=v;
            }while(now!=st);
        }
    }
    printf("%lld",solve(Bmx)-solve(Bmn-1));
    return 0;
}

然而,实际运行时,玄学的spfa竟然还快一些

posted @ 2019-03-01 09:10  mgnfcnt  阅读(211)  评论(0编辑  收藏  举报