zoukankan      html  css  js  c++  java
  • 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] (kin[0,1])\ f[i,j,k]=f[i-1,f[i-1,j,k],k] (i>1 kin[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] (kin[0,1])\ da[i,j,k]=da[i-1,j,k]+da[i-1,f[i-1,j,k],k] (i>1 kin[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] (kin[0,1])\ db[i,j,k]=db[i-1,j,k]+db[i-1,f[i-1,j,k],k] (i>1 kin[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
    ",ans);
        for(i=1;i<=m;++i) printf("%lld %lld
    ",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}) (iin [1,t]))拼接而成。

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

    然后考虑如何得到(conn(s_2,2^i) (iin [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
    ",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
    ",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
    ",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
    ",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
    ",f[n]);
        return 0;
    }
    
  • 相关阅读:
    iOS之内存管理(ARC)
    分布式锁1 Java常用技术方案
    谈谈如何使用Netty开发实现高性能的RPC服务器
    前后端分离开发模式下后端质量的保证 —— 单元测试
    jquery实现"跳到底部","回到顶部"效果
    html内容超出了div的宽度如何换行让内容自动换行
    采用easyui+ajax+htm+ashx编写 通过用户对应角色 角色对应菜单 控制用户的访问权限
    javascript [] 与 {} 的区别
    图说设计模式
    T4教程1 T4模版引擎之基础入门
  • 原文地址:https://www.cnblogs.com/Bhllx/p/10996519.html
Copyright © 2011-2022 走看看