0x57~0x59

0x57~0x59

0x57 倍增优化DP

a.[√] 开车旅行

题目传送

吐槽:

这道题是真的E心。。题面看了不知道多少遍吧,开始一直没看懂,然后怒写4k代码,几经绝望,,,乱搞过了

sol:

第一个首先要预处理出每一个城市的最近的城市和次近的城市,分别记为M1[],M2[]。

这个可以用链表解决,稍微表述一下:

首先把所有城市从小到大排一个序,把其建为一个链表。

然后把城市1~n在链表中的对应位置记录下来。

从1~n依次考虑每一个城市的最近和次近城市,

假设算到了i,其对应位置link[i],那么只需考虑link[i-1],link[i-2],link[i+1],link[i+2]即可。

最后算完i把i在链表中删掉即可。

其中有一些细节和需要注意的点,比如“本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近”,“超出n的范围”(后面同样需要注意)等,反正我是无脑怒刚了30行,,,

第二个的话需要考虑如何通过DP得到想要的信息。

首先如果已知出发城市,行车天数,谁先开车,就能得到结束城市。容易想到设\(f[i,j,0/1]\)表示这一结果。

但是由于ij都与n同阶的,所以用倍增优化一下(后同),即上式表示从j出发,a/b先开车,开\(2^i\)天后到达的城市。

那么,

\[f[0,j,0]=M2[j]\ \ f[0,j,1]=M1[j]\\ f[1,j,k]=f[0,f[0,j,k],k\ xor\ 1] \ (k\in[0,1])\\ f[i,j,k]=f[i-1,f[i-1,j,k],k]\ (i>1 \ \ \ k\in[0,1]) \]

然后得到f以后就可以分别计算出a,b的路程,

\(da[i,j,0/1]\)表示从j出发,a/b先开车,开\(2^i\)天后a走的路程,\(db[i,j,0/1]\)类似。

那么,

\[da[0,j,0]=dist(j,M2[j])\ \ da[0,j,1]=0\\ da[1,j,k]=da[0,j,k]+da[0,f[0,j,k],k\ xor\ 1] \ (k\in[0,1])\\ da[i,j,k]=da[i-1,j,k]+da[i-1,f[i-1,j,k],k]\ (i>1 \ \ \ k\in[0,1])\\ \]

\[db[0,j,0]=0\ \ da[0,j,1]=dist(j,M1[j])\\ db[1,j,k]=db[0,j,k]+db[0,f[0,j,k],k\ xor\ 1] \ (k\in[0,1])\\ db[i,j,k]=db[i-1,j,k]+db[i-1,f[i-1,j,k],k]\ (i>1 \ \ \ k\in[0,1])\\ \]

最后就是答案的计算了,用一个calc(S,X,0/1)表示从S出发最多走X米,a/b走的路程。

对于此考虑把路程从大到小扫描,把这个长度给拼凑出来即可,不多说了。

最后的最后,提醒:

由于本人脑抽+懒,没有把上述过程在代码中用函数把他们好好地体现出来,同时很多地方很蠢可以进行优化(但都懒得改),,,唉有点看不下去

code:


#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define int long long
#define DB double
using namespace std;

IL int gi() {
   RG int x=0,w=0; char ch=getchar();
   while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
   while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
   return w?-x:x;
}

const int N=1e5+3;
const int inf=2e9+7;

int f[19][N][2],da[19][N][2],db[19][N][2];
int n,m,ans,H[N],S[N],X[N],M1[N],M2[N],link[N];

struct List{int v,id,pre,next;}lis[N];
struct CITY{int h,id;}c[N];
IL bool cmp(CITY x,CITY y) {return x.h<y.h;}

IL int calc(int S,int X,int p) {
    RG int i,d[2]={0,0};
    for(i=18;i>=0;--i)
        if(f[i][S][0]&&d[0]+d[1]+da[i][S][0]+db[i][S][0]<=X)
            d[0]+=da[i][S][0],d[1]+=db[i][S][0],S=f[i][S][0];
    return d[p];
}

signed main()
{
    RG DB Min=1e18;
    RG int i,j,k,Id,pr,ne,d1,d2;
    for(i=1,n=gi();i<=n;++i) H[i]=gi();
    X[0]=gi(),m=gi();
    for(i=1;i<=m;++i) S[i]=gi(),X[i]=gi();
    for(i=1;i<=n;++i) c[i]=(CITY){H[i],i};
    sort(c+1,c+n+1,cmp);
    for(i=1;i<=n;++i) 
        lis[i]=(List){c[i].h,c[i].id,i-1,i+1},link[c[i].id]=i;
    lis[0].v=lis[n+1].v=2e9+1;
    for(i=1;i<=n;++i) {
        Id=link[i],pr=lis[Id].pre,ne=lis[Id].next;
        if(abs(lis[pr].v-H[i])<abs(lis[ne].v-H[i])) {
            M1[i]=lis[pr].id,pr=lis[pr].pre;
            if(abs(lis[pr].v-H[i])<abs(lis[ne].v-H[i])) M2[i]=lis[pr].id;
            else if(abs(lis[pr].v-H[i])>abs(lis[ne].v-H[i])) M2[i]=lis[ne].id;
            else {
                if(lis[pr].v<lis[ne].v) M2[i]=lis[pr].id;
                else M2[i]=lis[ne].id;
            }
        }
        else if(abs(lis[pr].v-H[i])>abs(lis[ne].v-H[i])) {
            M1[i]=lis[ne].id,ne=lis[ne].next;
            if(abs(lis[pr].v-H[i])<abs(lis[ne].v-H[i])) M2[i]=lis[pr].id;
            else if(abs(lis[pr].v-H[i])>abs(lis[ne].v-H[i])) M2[i]=lis[ne].id;
            else {
                if(lis[pr].v<lis[ne].v) M2[i]=lis[pr].id;
                else M2[i]=lis[ne].id;
            }
        }
        else {
            if(lis[pr].v<lis[ne].v) M1[i]=lis[pr].id,pr=lis[pr].pre;
            else M1[i]=lis[ne].id,ne=lis[ne].next;
            if(abs(lis[pr].v-H[i])<abs(lis[ne].v-H[i])) M2[i]=lis[pr].id;
            else if(abs(lis[pr].v-H[i])>abs(lis[ne].v-H[i])) M2[i]=lis[ne].id;
            else {
                if(lis[pr].v<lis[ne].v) M2[i]=lis[pr].id;
                else M2[i]=lis[ne].id;
            }		
        }
        pr=lis[Id].pre,ne=lis[Id].next;
        lis[pr].next=ne,lis[ne].pre=pr;
    }
    for(i=1;i<n;++i) f[0][i][0]=M2[i],f[0][i][1]=M1[i];
    for(i=1;i<=18;++i)
        for(j=1;j<=n;++j)
            if(j+(1<<i)<=n)
                for(k=0;k<=1;++k) 
                    if(i==1) f[1][j][k]=f[0][f[0][j][k]][k^1];
                    else f[i][j][k]=f[i-1][f[i-1][j][k]][k];
    memset(da,inf,sizeof(da)),memset(db,inf,sizeof(db));
    for(i=1;i<=n;++i) da[0][i][1]=0,da[0][i][0]=abs(H[M2[i]]-H[i]);
    for(i=1;i<=18;++i)
        for(j=1;j<=n;++j)
            if(j+(1<<i)<=n)
                for(k=0;k<=1;++k)
                    if(i==1) da[1][j][k]=da[0][j][k]+da[0][f[0][j][k]][k^1];
                    else da[i][j][k]=da[i-1][j][k]+da[i-1][f[i-1][j][k]][k];
    for(i=1;i<=n;++i) db[0][i][0]=0,db[0][i][1]=abs(H[M1[i]]-H[i]);
    for(i=1;i<=18;++i)
        for(j=1;j<=n;++j)
            if(j+(1<<i)<=n)
                for(k=0;k<=1;++k)
                    if(i==1) db[1][j][k]=db[0][j][k]+db[0][f[0][j][k]][k^1];
                    else db[i][j][k]=db[i-1][j][k]+db[i-1][f[i-1][j][k]][k];
    for(i=1;i<=n;++i) {
        d1=calc(i,X[0],0),d2=calc(i,X[0],1);
        if(d2==0) continue;
        else if((DB)d1<d2*Min) Min=(DB)d1/d2,ans=i;
    }
    if(i==n+1) printf("%lld\n",ans);
    for(i=1;i<=m;++i) printf("%lld %lld\n",calc(S[i],X[i],0),calc(S[i],X[i],1));
    return 0;
}

b.[√] Count The Repetitions

题目传送

sol:

倍增优化DP做法。

首先\(conn(conn(s_2,n_2),m)=conn(s_2,n_2*m)\),那么等价于求一个最大的m‘即可。

由于m'很大,考虑可以二进制分解一下:

如果\(m'=2^{p_{t}}+2^{p_{t-1}}+2^{p_{t-2}}…+2^{p_{1}}\),那么\(conn(s_2,m')\)可以看成\(conn(s_2,2^{p_i})\ (i\in [1,t])\)拼接而成。

此时相当于把原问题的一部分的规模缩小到了\(log_{m'}\)级别。

然后考虑如何得到\(conn(s_2,2^i)\ (i\in [0,log_2m'])\)

可以先假设\(s_1\)可以重复无限次,则只需考虑一个循环即可。

那么令\(f[i,j]\)表示从\(s_1[i]\)开始到能够构成\(conn(s_2,2^i)\)还至少需要多少个字符。

所以存在转移:

\[f[i,j]=f[i,j-1]+f[(i+f[i,j-1])\%|s_1|,j-1] \]

至于初值\(f[i,0]\)可以考虑直接BF做。

当把f值全部求出来之后,则只需要考虑枚举起点,

在字符数不超过\(|s_1|*n_1\)的前提下,把答案拼凑出来,并使其尽可能大即可。

code:

#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;

const int N=303;

char s1[N],s2[N];
LL ans,f[N][N];
int n1,n2,len1,len2;


int main()
{
   	while(cin>>s2>>n2>>s1>>n1) {
		// why can not use "scanf(...)" ,,
		RG int i,j,fl;
		memset(f,0,sizeof(f));
		len1=strlen(s1),len2=strlen(s2);
		for(i=0,fl=0;i<len1&&!fl;++i) {
			RG int pos=i;
			for(j=0;j<len2&&!fl;++j) {
				RG int cnt=0;
				while(s1[pos]!=s2[j]) {
					pos=(pos+1)%len1;
					if(++cnt>=len1) {fl=1;break;}
				}
				pos=(pos+1)%len1,f[i][0]+=cnt+1;
			}
		}
		if(fl) {puts("0");continue;}
		for(i=1;i<=30;++i)
			for(j=0;j<len1;++j) f[j][i]=f[j][i-1]+f[(j+f[j][i-1])%len1][i-1];
		for(i=0,ans=0;i<len1;++i) {
			RG LL pos=i,k=0;
			for(j=30;j>=0;--j)
				if(pos+f[pos%len1][j]<=n1*len1)
					pos+=f[pos%len1][j],k+=1<<j;
			ans=max(ans,k);
		}
		printf("%lld\n",ans/(LL)n2);
	}
    return 0;
}

0x58 数据结构优化DP

c.[√] Cleaning Shifts

题目传送

sol:

事实上直接跑最短路或许是最轻松的做法了。

但是数据结构优化DP也是没有问题的。

\(f[x]\)表示覆盖\([L,x]\)所需要的最小代价,那么存在转移:

\[f[b_i]=min_{a_i-1≤x<b_i}\{f[x]\}+c_i \]

可以发现需要维护区间最值和支持单点修改,一颗\([L-1,R]\)线段树即可。

code:

#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
#define lson p<<1
#define rson p<<1|1
using namespace std;

IL int gi() {
   RG int x=0,w=0; char ch=getchar();
   while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
   while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
   return w?-x:x;
}

const int N=1e5+3;
const int M=1e6+1;

int n,P,Q,f[M],Min[M<<2];

struct cows{int a,b,c;}s[N];
IL bool cmp(cows x, cows y) {return x.b<y.b;}

IL void pushup(int p) {Min[p]=min(Min[lson],Min[rson]);}

void build(int p,int l,int r) {
	if(l==r) {Min[p]=f[l];return;}
	RG int mid=l+r>>1;
	build(lson,l,mid),build(rson,mid+1,r);
	pushup(p);
}

void modify(int p,int l,int r,int pos) {
	if(l==r) {Min[p]=min(Min[p],f[pos]);return;}
	RG int mid=l+r>>1;
	if(pos<=mid) modify(lson,l,mid,pos);
	else modify(rson,mid+1,r,pos);
	pushup(p);
}

int query(int p,int l,int r,int L,int R) {
	if(L<=l&&R>=r) return Min[p];
	RG int mid=l+r>>1,ans=0x3f3f3f3f;
	if(L<=mid) ans=min(ans,query(lson,l,mid,L,R));
	if(R>mid) ans=min(ans,query(rson,mid+1,r,L,R));
	return ans;
}

int main()
{
	RG int i;
	n=gi(),P=gi()+1,Q=gi()+1;
	for(i=1;i<=n;++i)
		s[i].a=gi()+1,s[i].b=gi()+1,s[i].c=gi();
	sort(s+1,s+n+1,cmp);
	memset(f,0x3f,sizeof(f));
	f[P-1]=0; build(1,P-1,Q);
	for(i=1;i<=n;++i) {
		if(P>s[i].b||s[i].a>Q) continue;
		f[s[i].b]=query(1,P-1,Q,max(s[i].a,P)-1,min(s[i].b,Q))+s[i].c;
		if(s[i].b<=Q) modify(1,P-1,Q,s[i].b);
	}
	RG int ans=0x3f3f3f3f;
	for(i=1;i<=n;++i)
		if(s[i].b>=Q) ans=min(ans,f[s[i].b]);
	printf("%d\n",ans==0x3f3f3f3f?-1:ans);
    return 0;
}

d.[√] The Battle of Chibi

题目传送

sol:

容易考虑到令\(f[i,j]\)表示以\(A[j]\)为结尾的,长度为i的严格递增子序列的数量。

那么转移应该为:

\[f[i,j]=\sum_{k<j\&\&A_k<A_j}f[i-1,k] \]

复杂度\(O(n^3)\),考虑优化。

可以发现可以成为决策的k都在j之前可以考虑得到,只需要快速求出满足\(A_k<A_j\)的即可。

考虑可以建立一个权值树状数组,实际上即下标为它的A[]值大小的数组。

每次对于j,只需查询比\(A[j]\)小的那些和即可,得到结果之后在把\(f[i-1,j]\)插入数组即可。

由于A[]的范围较大,离散化即可。

code:

#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
#define lowbit(x) x&-x
using namespace std;

const int N=1003;
const int mod=1e9+7;

int n,m,a[N],b[N],c[N];
int ans,f[N][N],bit[N];

IL void add(int x,int v) {for(;x<=n;x+=lowbit(x)) bit[x]=(bit[x]+v)%mod;}
IL int query(int x) {RG int ans=0;for(;x;x-=lowbit(x)) ans=(ans+bit[x])%mod;return ans;}

int main()
{
   	RG int T,G,i,j;
	for(G=1,cin>>T;G<=T;++G) {
		cin>>n>>m; 
		for(i=1;i<=n;++i) cin>>a[i],b[i]=a[i];
		sort(b+1,b+n+1);
		for(i=1;i<=n;++i) a[i]=lower_bound(b+1,b+n+1,a[i])-b+1;
		memset(f,0,sizeof(f));
		a[0]=1,f[0][0]=1;
		for(i=1;i<=m;++i) {
			memset(bit,0,sizeof(bit));
			add(a[0],f[i-1][0]);
			for(j=1;j<=n;++j) f[i][j]=query(a[j]-1),add(a[j],f[i-1][j]);
		}
		for(i=1,ans=0;i<=n;++i) ans=(ans+f[m][i])%mod;
		printf("Case #%d: %d\n",G,ans);
	}
    return 0;
}

0x59 单调队列优化DP

d.[√] Fence

题目传送

sol:

首先可以按照\(s_i\)从小到大排序,保证可以进行顺序DP。

然后考虑设\(f[i,j]\)表示前i个工匠,粉刷到了第j块(可以有空缺的不刷)的最大收益。

那么:

\[f[i,j]=f[i-1,j]\ \ \ \ \ i工匠不刷\\ f[i,j]=f[i,j-1]\ \ \ \ \ j墙不刷\\ f[i,j]=max_{j-L_i≤k<s_i}\{f[i-1,k]+p_i*(j-k)\} \]

再把第三个式子转化一下:

\[f[i,j]=p_i*j+max_{j-L_i≤k<s_i}\{f[i-1,k]-p_i*k\} \]

可以发现需要维护的max中只涉及k,并且有可能成为决策的k存在范围限制,那么考虑用单掉队列维护最值。

既可以:先掐头,然后求值,然后去尾,最后插值即可;

也可以:现一次性把所有可能的决策插入,然后只需掐头和取值即可。

code:

#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;

IL int gi() {
   RG int x=0,w=0; char ch=getchar();
   while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
   while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
   return w?-x:x;
}

const int N=2e4+1;
const int M=103;

int n,m,q[N],f[M][N];

struct woker{int L,p,s;}wk[M];
IL bool cmp(woker A, woker B) {return A.s<B.s;}

IL int calc(int i,int k) {return f[i-1][k]-wk[i].p*k;}

int main()
{
   	RG int i,j;
	n=gi(),m=gi();
	for(i=1;i<=m;++i) wk[i].L=gi(),wk[i].p=gi(),wk[i].s=gi();
	sort(wk+1,wk+m+1,cmp);
	for(i=1;i<=m;++i) {
		RG int h=1,t=0;
		/*for(j=max(wk[i].s-wk[i].L,0);j<wk[i].s;++j) {
			while(h<=t&&calc(i,q[t])<=calc(i,j)) --t;
			q[++t]=j;
			}*/
		q[++t]=0;
		for(j=1;j<=n;++j) {
			f[i][j]=max(f[i-1][j],f[i][j-1]);
			if(wk[i].s<=j) {
				while(h<=t&&q[h]<j-wk[i].L) ++h;
				if(h<=t) f[i][j]=max(f[i][j],calc(i,q[h])+wk[i].p*j);
			}
			if(j<wk[i].s&&j>=max(wk[i].s-wk[i].L,0)) {
				while(h<=t&&calc(i,q[t])<=calc(i,j)) --t;
				q[++t]=j;
			}
		}
	}
	printf("%d\n",f[m][n]);
	return 0;
}

f.[√] Cut the Sequence

题目传送

sol:

容易考虑到令\(f[i]\)表示前i个数分成若干段的最小答案,那么存在转移:

\[f[i]={min}_{0≤j<i并且\sum_{k=j+1}^ia[k]≤m}\{f[j]+max_{j+1≤k<i}\{a[k]\}\} \]

发现这个DP不好一眼看出优化,,那么一步一步来:

先从优化\(max_{j+1≤k<i}\{a[k]\}\)入手,

考虑对于一个i,从大到小考虑每一个决策j,

可以发现集合中每次只会多出一个新的a值,如果之前的最值大于这个新加入的值,那么上式的值不变。

利用这一点,可以直接用一个变量维护\(max\)值,可以使原问题复杂度降为\(O(n^2)\)

考虑继续优化,

可以注意到f[]是非严格递增的,那么结合上面可以知道,在\(max_{j+1≤k<i}\{a[k]\}\)一定的情况下,使j尽量小肯定优。

那么进而可以发现,一个决策j可能成为最优决策,

① 要么\(a[j]=max_{j≤k<i}\{a[k]\}\),因为否则就有\(max_{j≤k<i}\{a[k]\}=max_{j+1≤k<i}\{a[k]\}\)

即存在\(f[i-1]+max_{j≤k<i}\{a[k]\}≤f[i]+max_{j+1≤k<i}\{a[k]\}\)显然j不优。

可以用一个单调队列维护所有可能的最优决策,但由于决策的结果并不具备单调性,所以用\(multiset\)映射一下每一个决策的结果即可。

② 要么j是满足\(\sum_{k=j+1}^i≤m\)最小的j,可以通过处理出每一个这样的位置,用合法的队首进行一次转移即可。

代码实现需要认真考虑,很巧妙的,

事实上结合代码可以发现,利用单调队列把这个问题分成了一段一段的,对问题进行了极大地优化。

code:

#include<set>
#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;

const int N=1e5+3;

LL m,f[N],sum[N];
int n,h,t,p,a[N],b[N],q[N];

multiset<LL> s;

int main()
{
	RG int i;
   	scanf("%d%lld",&n,&m);
	for(i=1;i<=n;++i) {
		scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
		if(a[i]>m) return puts("-1"),0;
	}
	h=1,t=0,p=1;
	for(i=1;i<=n;++i) {
		while(sum[i]-sum[p-1]>m) ++p;//最小的符合上面②的位置
		while(h<=t&&a[i]>=a[q[t]]) {
			if(h<t) s.erase(a[q[t]]+f[q[t-1]]);
			--t;
		}
		q[++t]=i;
		if(h<t) s.insert(a[i]+f[q[t-1]]);
		while(h<=t&&q[h]<p) {
			if(h<t) s.erase(a[q[h+1]]+f[q[h]]);
			++h;
		}
		f[i]=f[p-1]+a[q[h]];//先直接进行一次转移
		if(h<t) f[i]=min(f[i],*s.begin());
	}
	printf("%lld\n",f[n]);
    return 0;
}
posted @ 2019-06-10 10:56  薄荷凉了夏  阅读(562)  评论(0编辑  收藏  举报