zoukankan      html  css  js  c++  java
  • 同余最短路

    同余最短路

    判断形如

    [sum a_ix_i=h ]

    的方程是否有自然数解

    算法流程

    我们找到所有的(a_i)中的最小值作为模数,如果一个数(p)可以被构造,那么(p+k imes a_1)也就一定可以被构造

    也就是对于每一个余数(i),我们需要找到最小的,(p mod a_1=i)(p)

    每加上一个数(a_j),我们可以实现(pmod a_1=i)(pmod a_1=(i+a_j)mod a_1)的转化,所以连边(i o (i+a_j)mod a_1),边权为(a_j)

    跑Dijkstra即可

    例题

    P3403 跳楼机 模板题
    P2371 [国家集训队]墨墨的等式 模板题

    //P2371 [国家集训队]墨墨的等式
    int a[14],n;
    ll l,r;
    const int N=5e5+5;
    struct Edge{int to,next;ll len;}e[N<<4];
    int head[N],ecnt;
    inline int add(int a,int b,int mod){return (a+b)%mod;}
    inline void adde(int u,int v,ll w){
    	e[++ecnt]=(Edge){v,head[u],w};head[u]=ecnt;
    }
    priority_queue<pair<ll,int> > q;
    ll dis[N];bool vis[N];
    void dijkstra(int s){
    	memset(dis,0x3f,sizeof(dis));
    	dis[s]=0;
    	q.push(make_pair(0,s));
    	while(!q.empty()){
    		int u=q.top().second;
    		q.pop();
    		if(vis[u]==1)continue;
    		vis[u]=1;
    		for(int i=head[u];i;i=e[i].next){
    			int v=e[i].to;
    			if(dis[v]>dis[u]+e[i].len){
    				dis[v]=dis[u]+e[i].len;
    				if(!vis[v])
    					q.push(make_pair(-dis[v],v));
    			}
    		}
    	}
    }
    int main(){
    	scanf("%d %lld %lld",&n,&l,&r);
    	for(int i=1;i<=n;++i){
    		scanf("%d",&a[i]);
    		if(!a[i])--i,--n;
    	}
    	sort(a+1,a+1+n);
    	for(int i=2;i<=n;++i)
    		for(int j=0;j<a[1];++j)
    			adde(j,(j+a[i])%a[1],a[i]);
    	dijkstra(0);
    	ll ans=0;
    	for(int i=0;i<a[1];++i){
    		if(dis[i]>r)continue;
    		ll L=max(l,dis[i])-1;
    		ans+=((r-i)/a[1])-((L-i)/a[1]);
    	}
    	printf("%lld",ans);
    	return 0;
    }
    

    P4156 [WC2016]论战捆竹竿

    神仙题

    首先KMP求出border之后就转化成了裸的同余最短路,但是复杂度爆炸,考虑优化建图

    字符串的border有一个结论:

    • 所有的border可以被划分为不超过(O(log n))个等差数列

    (我太菜了不会证)

    等差,又是同余的关系

    设当前的等差数列为(x,x+d,x+2d,cdots,x+(l-1) imes d),也就是首项为(x),公差为(d),项数为(l)的等差数列,在(mod x)意义下,会把所有(mod x)的余数分成(gcd(x,d))个环

    对于每一个环,我们找到当前(dis)的最小值作为起点,(dp)更新剩下的。但是因为等差数列的长度有(l)的限制,并不是所有编号小于当前点的点都能够更新到自己。用单调队列来维护这个(l)的限制

    现在考虑处理多个等差数列之间的转化,也就是不同模数之间的转化

    设之前模数为(p),当前模数为(x)

    首先(dis_i)肯定可以转移到(dis_{dis_i mod x})上,同时我们还要考虑长为 (u) 的转移,我们可以将其看作首项为 (0),公差为 (u),长度为 (1) 的等差数列,用上面的类似方法完成即可,不需要单调队列。

    inline void Min(ll &a,ll b){if(a>b)a=b;}
    inline void Max(ll &a,ll b){if(a<b)a=b;}
    inline int add(int a,int b,int mod){return (a+b>=mod)?a+b-mod:a+b;}
    const int N=4e6+5;
    //单调queue 
    struct Queue{
    	ll val[N];int id[N],head,tail;
    	inline void init(){head=1;tail=0;}
    	inline void push(ll x,int y){
    		while(tail>=head&&val[tail]>=x)--tail;
    		val[++tail]=x;id[tail]=y;
    	}
    	inline void pop(int x){while(head<tail&&id[head]<=x)++head;}
    	inline ll front(){return val[head];}
    }q;
    // kmp
    int nxt[N],border[N],tot,n;
    inline void kmp(char *s){
    	for(int i=2,j=0;i<=n;++i){
    		while(j&&s[j+1]!=s[i])j=nxt[j];
    		if(s[j+1]==s[i])++j;
    		nxt[i]=j;
    	}
    	tot=0;
    	for(int i=nxt[n];i;i=nxt[i])border[++tot]=n-i;border[++tot]=n;border[tot+1]=0;
    }
    int T;
    ll m,dis[N],dis1[N];
    int buc[N];
    int curmod;
    inline void init(){
    	memset(dis,0x3f,sizeof(dis));
    	dis[0]=0;curmod=0;
    }
    inline void change_mod(int x){
    	if(!curmod){curmod=x;return;}
    	memcpy(dis1,dis,curmod<<3);
    	memset(dis,0x3f,x<<3);
    	for(int i=0;i<curmod;++i){
    		int pos=dis1[i]%x;
    		Min(dis[pos],dis1[i]);
    	}
    	int mx=__gcd(x,curmod);
    	for(int i=0;i<mx;++i){
    		int cnt=1;
    		buc[1]=i;
    		for(int pos=add(i,curmod,x);pos!=i;pos=add(pos,curmod,x))
    			buc[++cnt]=pos;
    		for(int i=1;i<=cnt;++i)buc[i+cnt]=buc[i];cnt<<=1;
    		for(int i=2;i<=cnt;++i)Min(dis[buc[i]],dis[buc[i-1]]+curmod);
    	}
    	curmod=x;
    }
    int buc1[N];
    inline void work(int x,int d,int len){
    	int mx=__gcd(x,d);
    	change_mod(x);
    	for(int i=0;i<mx;++i){
    		int cnt=1;
    		buc1[1]=i;
    		int st=1;
    		for(int pos=add(i,d,x);pos!=i;pos=add(pos,d,x)){
    			buc1[++cnt]=pos;
    			if(dis[pos]<dis[buc1[st]])st=cnt;
    		}
    		int cnt1=cnt;cnt=0;
    		for(int i=st;i<=cnt1;++i)buc[++cnt]=buc1[i];
    		for(int i=1;i<st;++i)buc[++cnt]=buc1[i];
    		q.init();
    		q.push(dis[buc[1]]-d,1);
    		for(int i=2;i<=cnt;++i){
    			int u=buc[i];
    			q.pop(i-len);
    			Min(dis[u],q.front()+1ll*i*d+1ll*x);
    			q.push(dis[u]-1ll*i*d,i);
    		}
    	}
    }
    char s[N];
    int main(){
    	T=read();
    	while(T--){
    		n=read();m=readll();
    		scanf("%s",s+1);
    		if(m<n){puts("0");continue;}
    		m-=n;
    		init();
    		kmp(s);
    		for(int i=1,j=1;i<=tot;i=j+1){
    			j=i;
    			while(j+1<=tot&&border[i+1]-border[i]==border[j+1]-border[j])++j;
    			work(border[i],max(0,border[i+1]-border[i]),j-i+1);
    		}
    		ll ans=0;
    		for(int i=0;i<curmod;i++)if(dis[i]<=m)
    			ans+=(m-dis[i])/curmod+1;
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    Python random模块下的常用函数小结
    MySQL 数据库连接
    LeetCode 376. 摆动序列 做题小结
    LeetCode 1005. K 次取反后最大化的数组和 做题小结
    LeetCode 455. 分发饼干 做题小结
    完美解决Python与anaconda之间的冲突问题
    LeetCode 122. 买卖股票的最佳时机 II 做题小结
    LeetCode 714. 买卖股票的最佳时机含手续费 做题小结
    如何去除有道云笔记广告(windows)
    baby web
  • 原文地址:https://www.cnblogs.com/harryzhr/p/14703457.html
Copyright © 2011-2022 走看看