T1--最长公共前缀(lcp)
定义两个字符串S,T 的最长公共前缀lcp(S,T)为最长的字符串R,满足R 既是S 的前缀又是T 的前缀。
给定一个字符串S,下标从1 开始,每次询问给出四个正整数a,b,c,d,你需要输出[a,b]这个子串与[c,d]这个子串的lcp 的长度。
解法
暴力60分不用说了,那么正解可以用扩展\(kmp\),或者是万能字符串处理方法\(hash\)。但是我不会扩展\(kmp\),所以我就只用\(hash\)来乱搞一下。
那么二分查找这个前缀的长度,每次用\(hash\)检查一下,总的时间复杂度就是\(O(nlogn)\)。
ac代码
# include <cstdio>
# include <cstring>
# include <algorithm>
# include <ctype.h>
# include <iostream>
# include <cmath>
# include <map>
# include <vector>
# include <queue>
# define LL long long
# define ms(a,b) memset(a,b,sizeof(a))
# define ri (register int)
# define inf (0x7f7f7f7f)
# define pb push_back
# define fi first
# define se second
# define pii pair<int,int>
using namespace std;
inline int gi(){
int w=0,x=0;char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return w?-x:x;
}
# define N 100005
const int Mod=1e9+7,Base=127;
LL f[N],hash[N];
int n,m;
char s[N];
LL get_hash(int l,int r){
return (f[r]-f[l-1]*hash[r-l+1]%Mod+Mod)%Mod;
}
int query(int a,int b,int c,int d){
int l=1,r=b-a+1,res=0;
if (d-c+1<r) r=d-c+1;
while (l<=r){
int mid=(l+r)>>1;
if (get_hash(a,a+mid-1)==get_hash(c,c+mid-1)) l=mid+1,res=mid;
else r=mid-1;
}
return res;
}
int main(){
n=gi(),m=gi();
scanf("%s",s+1);
hash[0]=1;
for (int i=1;i<=n;i++) hash[i]=hash[i-1]*(LL)Base%Mod;
for (int i=1;i<=n;i++) f[i]=(f[i-1]*(LL)Base+s[i])%Mod;
while (m--){
int a=gi(),b=gi(),c=gi(),d=gi();
printf("%d\n",query(a,b,c,d));
}
return 0;
}
T2--抢救粮仓(save)
查尔明的家着火啦!他的粮仓正面临着被烧毁的危险!
查尔明在山上一共建有n 个粮仓,按海拔从高到低依次被标记上编号1 到n,且没有任意两个粮仓的海拔是一样的。第i 个粮仓里藏有a[i]吨粮食,第i 个粮仓与第i-1 个粮仓之间的距离为1。
查尔明立马掏出水枪进行灭火,每次灭火他可以指定一个粮仓进行抢救,由于不同粮仓的火势不同,需要喷的水量也不同,抢救第i 个粮仓需要消耗b[i]吨水。查尔明的手速实在是太快了,你可以理解成灭火是瞬间完成的。与此同时,查尔明的助手们开着货车去转移粮食,因为他们认为越低的地方越安全,所以他们只会把粮食从高处运往低处,最终运到某个被灭过火的粮仓。查尔明的粮食很奇怪,每个粮仓都有一个共享值d[i],表示第i 个粮仓只能容纳与距自己距离不超过d[i]的粮仓的粮食,否则就会发生爆炸,所以在运输的过程中务必要避免发生爆炸。
已知水费和粮食运输费都为1 元/吨,查尔明想知道,自己最少需要花费多少钱来抢救全部粮食?
解法
一开始没有想到用dp,就打了一个三个特判。当\(n<=1000\)时,按照从低到高依次遍历,如果可以直接灭掉,那么就直接灭掉,然后再用这个点来更新后面的其他节点,我们更新的就是最小值,在判断。第二种情况是\(d_i=n\),那么也就是所有的节点都可以更新其他的所有节点,那么就暴力取最小值。
但是这只是\(70\)分,那么如果要拿到接下来的30分,我们就需要用\(dp\),\(f[i]\)表示前\(i\)个仓库需要的最小代价。转移方程就是:\(f[i]=min(f[j]-sum[j])+sum[i-1]+b[i](j>=i-d[i]-1)\),这个意思就是可以从\(j\),这个j是在i的范围内,加上他能够带来的代价,再取上最小值。
这个最小值可以用单调队列来维护或者是用树状数组或者是线段树来维护。
ac代码
# include <cstdio>
# include <cstring>
# include <algorithm>
# include <ctype.h>
# include <iostream>
# include <cmath>
# include <map>
# include <vector>
# include <queue>
# define LL long long
# define ms(a,b) memset(a,b,sizeof(a))
# define ri (register int)
# define inf (0x7f7f7f7f)
# define pb push_back
# define fi first
# define se second
# define pii pair<int,int>
using namespace std;
inline int gi(){
int w=0,x=0;char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return w?-x:x;
}
# define N 100005
int q[N],f[N],a[N],b[N],d[N],sum[N];
int n,tail,head;
int find(int x){
int l=head,r=tail-1,res;
while (l<=r){
int mid=(l+r)>>1;
if (q[mid]<x) l=mid+1;
else r=mid-1,res=mid;
}
return q[res];
}
int main(){
n=gi();
for (int i=1;i<=n;i++) a[i]=gi(),sum[i]=sum[i-1]+a[i];
for (int i=1;i<=n;i++) b[i]=gi();
for (int i=1;i<=n;i++) d[i]=gi();
q[tail++]=0;
for (int i=1;i<=n;i++){
int p=find(i-d[i]-1);
f[i]=f[p]-sum[p]+b[i]+sum[i-1];
while (head<tail&&f[q[tail-1]]-sum[q[tail-1]]>=f[i]-sum[i]) tail--;
q[tail++]=i;
}
printf("%d\n",f[n]);
return 0;
}
T3--幸运7(seven)
查尔明为了锻炼自己的观察力,在纸上画了一棵有n 个节点的树,点与点之间通过边连接,每条边有一个正整数权值。查尔明想要用肉眼观察出有多少组(i,j)满足1<=i<j<=n,且i 到j 的距离是他的幸运数7 的倍数。
查尔明不一会儿就观察出来了,但是他不知道自己的答案是否正确,你能写一个程序帮助他检验自己的答案吗?
解法
这道题太水了,原题是聪聪可可,点分治或者是树形dp。
点分治在我的xio讲堂里有讲到过,讲一下如何用树形dp来做。
定义:\(f[i][j]\)表示以第\(i\)为根节点的子树,到根节点距离除以7的余数\(j\)的节点的个数。
转移方程就是:\(f[i][j]=f[son][((j-len)%7+7%7]\),\(son\)是\(i\)节点的儿子,\(len\)是\(i\)到\(son\)之间的长度。
答案\(ans\)需要在递归的时候累加出来,\(ans=f[son][(-j-len)%7]*f[u][j]\),。
ac代码
# include <cstdio>
# include <cstring>
# include <algorithm>
# include <ctype.h>
# include <iostream>
# include <cmath>
# include <map>
# define LL long long
# define ms(a,b) memset(a,b,sizeof(a))
# define ri (register int)
# define inf (0x7f7f7f7f)
# define pb push_back
# define fi first
# define se second
# define pii pair<int,int>
using namespace std;
inline int gi(){
int w=0,x=0;char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return w?-x:x;
}
# define N 100005
struct edge{
int to,nt,w;
}E[N<<1];
LL f[N][7];
int H[N];
int cnt,n;
LL ans;
void addedge(int u,int v,int w){
E[++cnt]=(edge){v,H[u],w}; H[u]=cnt;
}
void dfs(int u,int fa){
f[u][0]=1;
for (int e=H[u];e;e=E[e].nt){
int v=E[e].to,w=E[e].w;
if (v==fa) continue;
dfs(v,u);
for (int i=0;i<7;i++) ans+=f[v][i]*f[u][((-i-w)%7+7)%7]*2;
for (int i=0;i<7;i++) f[u][((i+w)%7+7)%7]+=f[v][i];
}
}
int main(){
n=gi();
for (int i=1;i<n;i++){
int u=gi(),v=gi(),w=gi();
addedge(u,v,w); addedge(v,u,w);
}
ans=0; dfs(1,0);
printf("%lld\n",ans/2);
return 0;
}