HGOI 20190303 题解
Posted ljc20020730
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HGOI 20190303 题解相关的知识,希望对你有一定的参考价值。
/* 记一串数字真难。 今天比赛又是hjcAK的一天。 今天开题顺序是312,在搞T1之前搞了T3 昨天某谷月赛真是毒瘤。 但是讲评的同学不错,起码T4看懂了... 构造最优状态然后DP的思路真妙 */
Problem A lcp
给出字符串S,m个询问,每个询问含有$l1,r1,l2,r2$求|S|子串$[l1,r1]$和$[l2,r2]$的LCP(最长公共前缀)
对于100%的数据$ 1 leq |S|,m leq 10^5 , l1 leq r1 ,l2 leq r2$
考虑二分答案套字符串Hash,于是就不用KMP了(我不会KMP)
再说一下Hash的思路吧hash[i]表示S前i个字符的前缀哈希值,设基底为E=$31$,模数mo=$10^9+9$
令hash[0]=0;对于$i geq 1 , 计算方法如下 :$ hash[i]=hash[i-1] imes E+Val(s[i]) $ Val(x)是一个映射把char类型的x映射成一个int类型
所以利用前缀和的思想,如果不计模造成的负数问题$ Hash(l,r)=hash[r]-hash[l-1] imes E^{r-l+1} $
如果考虑模数造成负数问题那么要多mo几次,即 Hash(l,r) = ( (hash[r] - (hash[l-1] * pow[r-l+1] % mo) ) % mo + mo) % mo
得函数Hash(l,r)表示串S的子串[l,r]的哈希值。
那么这样就可以O1判断两个子串是不是相等了。套个2分就过了。
# include <bits/stdc++.h> # define int long long # define hash HASH # define pow Pow using namespace std; const int N=1e5+10; const int mo=1e9+7; const int E=51; char s[N]; int n,m,hash[N],pow[N]; inline int read() { int X=0,w=0; char c=0; while(c<‘0‘||c>‘9‘) {w|=c==‘-‘;c=getchar();} while(c>=‘0‘&&c<=‘9‘) X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } int val(char ch){return ch-‘a‘;} int Hash(int l,int r){ return ((hash[r]-hash[l-1]*pow[r-l+1]%mo)%mo+mo)%mo; } bool check(int len,int l1,int l2){ if ((int) Hash(l1,l1+len-1)==(int) Hash(l2,l2+len-1)) return 1; else return 0; } signed main() { freopen("lcp.in","r",stdin); freopen("lcp.out","w",stdout); n=read();m=read(); scanf("%s",s+1); pow[0]=1; for (int i=1;i<=n;i++) pow[i]=pow[i-1]*E%mo; for (int i=1;i<=n;i++) hash[i]=(hash[i-1]*E+val(s[i]))%mo; int l1,r1,l2,r2; while (m--) { l1=read();r1=read();l2=read();r2=read(); int l=0,r=min(r1-l1+1,r2-l2+1),ans=0; while (l<=r) { int mid=(l+r)>>1; if (check(mid,l1,l2)) ans=mid,l=mid+1; else r=mid-1; } printf("%lld ",ans); } return 0; }
Problem B Save
现在有n个粮仓,从高到低且间距是1被排在一个山坡上。对于一个粮仓可以进行扑灭和转移操作。
其中扑灭操作的代价是 b[i] , 转移操作的代价是a[i] (转移到任何一个高度较低的已经执行过扑灭操作的粮仓且满足距离差$Delta d leq $d[i])
对于 n 个粮仓执行完毕两个操作中的一个,求最小代价。
对于100%的数据$n leq 10^5 , a[i],b[i] leq 100 $
首先考虑一个问题第n个粮仓是一定是需要扑灭的。
那么f[i]就表示为前i个粮仓不被烧毁时候的最小代价,那么显然第i个粮仓的操作是不能为转运。
那么f[i]必然满足第i个粮仓是扑灭操作的。
我们考虑f[i]从f[j]转移过来,其中j应该是一个可以被转移过来的值,即$max(0,i - d[i] - 1) leq j < i $
那么对于[j+1,i)这么多个粮仓,每一个粮仓既可以选择扑灭,又可以选择转移到第i个粮仓。
考虑一个简单的贪心如果转运更划算那么就转运,如果直接扑灭更划算那么扑灭,这样仅对于[j+1,i)的处理是局部最优的。
那么f[i] 从 f[j] 转移过来的 大家是最小的, 我们不用考虑其他情况的代价 , 因为在这次转移中 我们求的是最小值, 在所有转移中的最小值就是 f[i]的最优值。
即$f_i = minlimits_{max(0,i-d_i-1)leq j<i} { f_j + sumlimits_{k=j+1}^{i-1} min { a_k , b_k } + b_k}$
显然可以前缀和优化到$O(n^2)$
即 $ 令 S_i = sumlimits _{j=1}^i min { a_j , b_j }$
转移改写为 $f_i= min limits_{max(0,i-d_i-1)leq j <i} { f_j-s_j } +b_i +s_{i-1} $
观察这个dp式子发现对于一个确定的j,$f_j-s_j$ 是一个定值,而后面和j没有关系只和i有关系,可看做常量,
可以使用数据结构线段树 (或者二分法优化) , 即记录 区间max值,注意下标不能为0所以所有在线段树中下标+1
# include <bits/stdc++.h> # define fp(i,s,t) for (int i=s;i<=t;i++) # define inf (0x3f3f3f3f) using namespace std; const int N=1e5+10; int a[N],b[N],d[N],s[N],n,f[N],c[N<<2]; inline int read() { int X=0,w=0; char c=0; while(c<‘0‘||c>‘9‘) {w|=c==‘-‘;c=getchar();} while(c>=‘0‘&&c<=‘9‘) X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } void update(int x,int l,int r,int pos,int opx) { if (l==r) {c[pos]=opx;return;} int mid=l+r>>1; if (pos<=mid) update(2*x,l,mid,pos,opx); else update(2*x+1,mid+1,r,pos,x); c[x]=min(c[2*x],c[2*x+1]); } int query(int x,int l,int r,int opl,int opr) { if (opl<=l&&r<=opr) return c[x]; int mid=l+r>>1,ret=inf; if (opl<=mid) ret=min(ret,query(2*x,l,mid,opl,opr)); if (opr>mid) ret=min(ret,query(2*x+1,mid+1,r,opl,opr)); return ret; } int main() { freopen("save.in","r",stdin); freopen("save.out","w",stdout); n=read(); fp(i,1,n) a[i]=read(); fp(i,1,n) b[i]=read(); fp(i,1,n) d[i]=read(),s[i]=s[i-1]+min(a[i],b[i]); memset(f,0x3f,sizeof(f)); f[0]=0; for (int i=1;i<=n;i++) { f[i]=query(1,1,n,max(0,i-d[i]-1)+1,i-1+1)+b[i]+s[i-1]; update(1,1,n,i+1,f[i]-s[i]); } printf("%d ",f[n]); return 0; }
以上是关于HGOI 20190303 题解的主要内容,如果未能解决你的问题,请参考以下文章