zoukankan      html  css  js  c++  java
  • UOJ#172. 【WC2016】论战捆竹竿 字符串 KMP 动态规划 单调队列 背包

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ172.html

    题解

    首先,这个问题显然是个背包问题。

    然后,可以证明:一个字符串的 border 长度可以划分成 $O(log |S|)$ 个等差数列。

    (以下图片摘自  金策 - 《字符串算法选讲》)

    由于长度 n 可以随便取,所以我们可以在对n取模的意义下做背包,设 dis[i] 为 占用背包容量%n = i 时至少要占用多少背包容量,那么直接建 $n^2$ 条边跑一下 dijkstra 就可以在 $O(n^2log n )$ 的时间复杂度内解决此题。

    对于所有的长度,我们将其分组,保证每一组是一个等差数列。

    我们枚举等差数列,依次将其加入背包。

    假设最终背包中最多包含当前等差数列的一个元素,那么比较好做,直接单调队列就好了。

    但是可以有多个当前等差数列的元素计入最终背包容量。也就是说,假设当前等差数列用 $v + kd$ 表示,那么可能会有 $tv + sum k_i d$ 这种情况。这在对 n 取模的意义下非常难做。

    于是我们考虑把它转化成对 $v$ 取模的意义下的结果,这样就方便多了。

    考虑如何转化:先把原先在对 n 取模意义下的结果放到对 v 取模意义下的数组里,然后考虑每一个 $dis'[i]$ 都可以更新 $dis'[(i+n)mod v ]$,即 $dis'[(i+n)mod v] =min(dis'[(i+n)mod v],dis'[i] + n)$ 。

    把转移形成的环搞出来,dp转移一下就可以得到新的dis数组。

    剩下的单调队列+dp也类似。

    于是时间复杂度是 $O(nlog n)$ 。

    代码

    #pragma GCC optimize("Ofast","inline")
    #include <bits/stdc++.h>
    #define clr(x) memset(x,0,sizeof (x))
    #define fi first
    #define se second
    using namespace std;
    typedef long long LL;
    LL read(){
    	LL x=0,f=0;
    	char ch=getchar();
    	while (!isdigit(ch))
    		f|=ch=='-',ch=getchar();
    	while (isdigit(ch))
    		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    	return f?-x:x;
    }
    const int N=500005;
    const LL INF=2e18;
    int T,n,m;
    LL w;
    int Fail[N],nf[N];
    struct brd{
    	int v,d,c;
    }b[N];
    char s[N];
    LL dis[N],rng;
    LL gcd(LL a,LL b){
    	return b?gcd(b,a%b):a;
    }
    void Trans(LL nr){
    	static LL d[N];
    	for (int i=0;i<nr;i++)
    		d[i]=INF;
    	for (int i=0;i<rng;i++)
    		d[dis[i]%nr]=min(d[dis[i]%nr],dis[i]);
    	int g=gcd(rng,nr);
    	for (int t=0;t<g;t++){
    		int e=nr/g*2,_i=t,rg=rng%nr;
    		for (LL i=t,j=0;j<e;i+=rng,j++){
    			d[_i]=min(d[_i],d[_i<rg?_i-rg+nr:_i-rg]+rng);
    			if ((_i+=rg)>=nr)
    				_i-=nr;
    		}
    	}
    	rng=nr;
    	for (int i=0;i<rng;i++)
    		dis[i]=d[i];
    }
    pair <LL,LL> Q[N*2];
    int head,tail;
    void DP(int d,int c){
    	int g=gcd(rng,d);
    	for (int t=0;t<g;t++){
    		head=1,tail=0;
    		int e=rng/g*2,_i=t;
    		for (LL i=t,j=0;j<e;i+=d,j++){
    			if (head<=tail&&Q[head].fi+(LL)c*d<=i)
    				head++;
    			if (head<=tail)
    				dis[_i]=min(dis[_i],Q[head].se+rng+i);
    			while (head<=tail&&Q[tail].se>=dis[_i]-i)
    				tail--;
    			Q[++tail]=make_pair(i,dis[_i]-i);
    			if ((_i+=d)>=rng)
    				_i-=rng;
    		}
    	}
    }
    int main(){
    	T=read();
    	while (T--){
    		n=read(),w=read();
    		scanf("%s",s+1);
    		Fail[1]=0;
    		for (int i=2;i<=n;i++){
    			int k=Fail[i-1];
    			while (k>0&&s[k+1]!=s[i])
    				k=Fail[k];
    			if (s[k+1]==s[i])
    				k++;
    			Fail[i]=k;
    		}
    		m=0;
    		for (int i=n;i>0;i=Fail[i])
    			nf[Fail[i]]=i;
    		for (int j=0;j<n;j=nf[j]){
    			int i=n-j;
    			if (j==0)
    				m++,b[m].v=i,b[m].d=0,b[m].c=1;
    			else if (!b[m].d)
    				b[m].d=b[m].v-i,b[m].c++,b[m].v=i;
    			else if (b[m].v-b[m].d==i)
    				b[m].v=i,b[m].c++;
    			else
    				m++,b[m].v=i,b[m].d=0,b[m].c=1;
    		}
    		if (!b[m].d)
    			b[m].d=1;
    		clr(dis);
    		rng=n;
    		for (int i=0;i<n;i++)
    			dis[i]=INF;
    		dis[0]=n;
    		for (int i=1;i<=m;i++){
    			Trans(b[i].v);
    			DP(b[i].d,b[i].c);
    		}
    		LL ans=0;
    		for (int i=0;i<rng;i++)
    			if (dis[i]<=w)
    				ans+=w/rng-dis[i]/rng+(w%rng>=dis[i]%rng?1:0);
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    python操作mysql封装成类
    es 数据 导出 到 MySQL
    Elasticsearch的数据导出和导入操作(elasticdump工具),以及删除指定type的数据(delete-by-query插件)
    解决VM虚拟机中的ubuntu不能全屏的问题
    pandas操作,感觉不错,复制过来的
    BTree和B+Tree详解
    ant安装配置
    jmeter默认生成测试报告
    学习网站
    selenium多窗口切换(windows)
  • 原文地址:https://www.cnblogs.com/zhouzhendong/p/UOJ172.html
Copyright © 2011-2022 走看看