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以后就可以分别计算出a,b的路程,
令(da[i,j,0/1])表示从j出发,a/b先开车,开(2^i)天后a走的路程,(db[i,j,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,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])所需要的最小代价,那么存在转移:
可以发现需要维护区间最值和支持单点修改,一颗([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的严格递增子序列的数量。
那么转移应该为:
复杂度(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块(可以有空缺的不刷)的最大收益。
那么:
再把第三个式子转化一下:
可以发现需要维护的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个数分成若干段的最小答案,那么存在转移:
发现这个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;
}