• 【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竟然还快一些

  • 相关阅读:
    BUG记录之 Database Connection Can’t Be Open!
    C#基础拾遗03注册表保存用户设置
    JQuery Ajax小磨合1
    SQL Server几个常用Date函数(二)
    浅谈设计模式01策略模式
    C#基础拾遗02XML串行化
    SQL Server 2008 R2学习心得
    WebService重载问题
    SQL Server几个常用date函数(一)
    C#获取打印机列表
  • 原文地址:https://www.cnblogs.com/mgnfcnt/p/10454543.html
Copyright © 2020-2023  润新知