ZOJ Monthly, January 2018 训练部分解题报告
A是水题,此处略去题解
B - PreSuffix ZOJ - 3995 (fail树+LCA)
给定多个字符串,每次询问查询两个字符串的一个后缀,该后缀必须是所有字符串中某个字符串的前缀,问该后缀最长时,是多少个字符串的前缀。
思路:对所有串构造ac自动机,根据fail指针的性质,a节点的fail指针指向b时,b一定是a的某个后缀。所以每次询问对两个字符串对应的节点在fail树上求一下LCA,插入时经过了LCA节点的字符串的个数便是答案。
#include<bits/stdc++.h> using namespace std; #define ll long long const int kind = 26; #define mem(x) memset(x,0,sizeof x) const int maxn=500005; int trie[maxn][30]; int p_cnt; int coun[maxn]; char s[maxn]; int mp[maxn]; int fail[maxn]; vector<int> G[maxn]; int f[maxn],d[maxn],siz[maxn],son[maxn],rk[maxn],top[maxn],tid[maxn],cnt; inline void init() { p_cnt=1; cnt=0; mem(trie),mem(coun),mem(fail),mem(mp); mem(f),mem(d),mem(siz),mem(son),mem(rk),mem(top),mem(tid); } void insert(char *str,int num) { int p=1; int i=0,index; while(str[i]) { index=str[i]-'a'; if(trie[p][index]==0) trie[p][index]=++p_cnt; p=trie[p][index]; coun[p]++; i++; } mp[num]=p; } inline void addedge(int x,int y) { G[x].push_back(y); G[y].push_back(x); } void build_ac_automation() { deque<int>dq; int i; fail[1]=0; dq.push_back(1); while(!dq.empty()) { int tmp=dq.front(); dq.pop_front(); int p=0; for(i=0; i<kind; i++) { if(trie[tmp][i]) { if(tmp==1) { fail[trie[tmp][i]]=1; addedge(trie[tmp][i],1); } else { p=fail[tmp]; while(p!=0) { if(trie[p][i]!=0) { fail[trie[tmp][i]]=trie[p][i]; addedge(trie[tmp][i],trie[p][i]); break; } p=fail[p]; } if(p==0) { fail[trie[tmp][i]]=1; addedge(trie[tmp][i],1); } } dq.push_back(trie[tmp][i]); } } } } void dfs1(int u,int fa,int depth) { f[u]=fa; d[u]=depth; siz[u]=1; for(int i=0; i<G[u].size(); i++) { int v=G[u][i]; if(v==fa) continue; dfs1(v,u,depth+1); siz[u]+=siz[v]; if(siz[v]>siz[son[u]]) son[u]=v; } } void dfs2(int u,int t) { top[u]=t; tid[u]=++cnt; rk[cnt]=u; if(!son[u]) return; dfs2(son[u],t); for(int i=0; i<G[u].size(); i++) { int v=G[u][i]; if(v!=son[u]&&v!=f[u]) dfs2(v,v); } } int LCA(int x,int y) { if(x==y) return x; int fx=top[x],fy=top[y]; while(fx!=fy) { if(d[fx]>=d[fy]) { x=f[fx]; } else { y=f[fy]; } fx=top[x]; fy=top[y]; } if(tid[x]<=tid[y]) return x; else return y; } #define debug cout<<"debug\n" int main() { //freopen("in.txt","r",stdin); int n,q; while(scanf("%d",&n)!=EOF) { init(); for(int i=1;i<=n;i++) { scanf("%s",s); insert(s,i); } for(int i=1;i<=p_cnt;i++) G[i].clear(); build_ac_automation(); dfs1(1,0,1); dfs2(1,1); scanf("%d",&q); while(q--) { int x,y; scanf("%d%d",&x,&y); x=mp[x]; y=mp[y]; int z=LCA(x,y); if(coun[z]==0) puts("N"); else printf("%d\n",coun[z]); } } return 0; }
E - Yet Another Data Structure Problem ZOJ - 3998 (线段树)
给定一个长度为N的数列,每次询问有三种操作:
1.将区间[l,r]中的每个元素乘上v
2.将区间[l,r]中的每个元素变为原来的k次方
3.求[l,r]中每个数的乘积对1e9+7取模.
一道相对较裸的数据结构题。用一棵线段树维护区间乘积,用两个lazy印记来标记修改,对于操作1,相当于区间乘积乘上v^(r-l+1),对于操作2,相当于区间中的乘积变为原来的k次方即可。利用快速幂取模以及a^b%mod=a^(b%(mod-1))%mod来避免指数爆long long,以及注意细节即可。(注意两个印记破坏的先后顺序)
大常数制造机
#include<bits/stdc++.h> using namespace std; #define ll long long const int maxn=1e5+5; const int mod=1e9+7; ll ans[maxn*4]; ll tag1[maxn*4]; ll tag2[maxn*4]; ll a[maxn]; inline ll q_p(ll a,ll b) { ll ans=1; a%=mod; while(b) { if(b&1) ans=a*ans%mod; b>>=1; a=a*a%mod; } return ans; } void build(int o,int l,int r) { tag1[o]=tag2[o]=1; if(l==r) { ans[o]=a[l]; return; } int lson=o<<1,rson=lson|1; int m=(l+r)>>1; build(lson,l,m); build(rson,m+1,r); ans[o]=(ans[lson]*ans[rson])%mod; } void update1(int o,int l,int r,int ql,int qr,int k)//区间修改:将[l,r]区间每个数*k { int lson=o<<1,rson=lson|1; int m=(l+r)>>1; if(ql<=l&&qr>=r) { tag1[o]*=k; tag1[o]%=mod; ans[o]*=q_p(k,r-l+1); ans[o]%=mod; return; } if(tag2[o]!=1) { tag2[lson]*=tag2[o]; tag2[rson]*=tag2[o]; tag2[lson]%=mod-1; tag2[rson]%=mod-1; ans[lson]=q_p(ans[lson],tag2[o]); ans[rson]=q_p(ans[rson],tag2[o]); tag1[lson]=q_p(tag1[lson],tag2[o]); tag1[rson]=q_p(tag1[rson],tag2[o]); tag2[o]=1; } if(tag1[o]!=1) { tag1[lson]*=tag1[o]; tag1[rson]*=tag1[o]; tag1[lson]%=mod; tag1[rson]%=mod; ans[lson]*=q_p(tag1[o],(m-l+1)); ans[rson]*=q_p(tag1[o],(r-m)); ans[lson]%=mod; ans[rson]%=mod; tag1[o]=1; } if(qr<=m) update1(lson,l,m,ql,qr,k); else if(ql>m) update1(rson,m+1,r,ql,qr,k); else { update1(lson,l,m,ql,qr,k); update1(rson,m+1,r,ql,qr,k); } ans[o]=ans[lson]*ans[rson]%mod; } void update2(int o,int l,int r,int ql,int qr,int k)//区间修改:将[l,r]区间每个数变为k次方 { int lson=o<<1,rson=lson|1; int m=(l+r)>>1; if(ql<=l&&qr>=r) { tag1[o]=q_p(tag1[o],k); tag2[o]*=k; tag2[o]%=mod-1; ans[o]=q_p(ans[o],k); return; } if(tag2[o]!=1) { tag2[lson]*=tag2[o]; tag2[rson]*=tag2[o]; tag2[lson]%=mod-1; tag2[rson]%=mod-1; ans[lson]=q_p(ans[lson],tag2[o]); ans[rson]=q_p(ans[rson],tag2[o]); tag1[lson]=q_p(tag1[lson],tag2[o]); tag1[rson]=q_p(tag1[rson],tag2[o]); tag2[o]=1; } if(tag1[o]!=1) { tag1[lson]*=tag1[o]; tag1[rson]*=tag1[o]; tag1[lson]%=mod; tag1[rson]%=mod; ans[lson]*=q_p(tag1[o],(m-l+1)); ans[rson]*=q_p(tag1[o],(r-m)); ans[lson]%=mod; ans[rson]%=mod; tag1[o]=1; } if(qr<=m) update2(lson,l,m,ql,qr,k); else if(ql>m) update2(rson,m+1,r,ql,qr,k); else { update2(lson,l,m,ql,qr,k); update2(rson,m+1,r,ql,qr,k); } ans[o]=ans[lson]*ans[rson]%mod; } ll query(int o,int l,int r,int ql,int qr)//区间查询 { int lson=o<<1,rson=lson|1; int m=(l+r)>>1; if(ql<=l&&qr>=r) return ans[o]; if(tag2[o]!=1) { tag2[lson]*=tag2[o]; tag2[rson]*=tag2[o]; tag2[lson]%=mod-1; tag2[rson]%=mod-1; ans[lson]=q_p(ans[lson],tag2[o]); ans[rson]=q_p(ans[rson],tag2[o]); tag1[lson]=q_p(tag1[lson],tag2[o]); tag1[rson]=q_p(tag1[rson],tag2[o]); tag2[o]=1; } if(tag1[o]!=1) { tag1[lson]*=tag1[o]; tag1[rson]*=tag1[o]; tag1[lson]%=mod; tag1[rson]%=mod; ans[lson]*=q_p(tag1[o],(m-l+1)); ans[rson]*=q_p(tag1[o],(r-m)); ans[lson]%=mod; ans[rson]%=mod; tag1[o]=1; } if(qr<=m) return query(lson,l,m,ql,qr); if(ql>m) return query(rson,m+1,r,ql,qr); return query(lson,l,m,ql,qr)*query(rson,m+1,r,ql,qr)%mod; } inline void init() { memset(ans,0,sizeof ans); memset(tag1,0,sizeof tag1); memset(tag2,0,sizeof tag2); memset(a,0,sizeof a); } int main() { int t; scanf("%d",&t); while(t--) { init(); int n,q; scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) scanf("%lld",a+i); build(1,1,n); while(q--) { int op,l,r; scanf("%d%d%d",&op,&l,&r); if(op==1) { ll k; scanf("%lld",&k); update1(1,1,n,l,r,k); } if(op==2) { ll k; scanf("%lld",&k); update2(1,1,n,l,r,k); } if(op==3) { printf("%lld\n",query(1,1,n,l,r)); } } } }
H - Traveling Plan ZOJ - 4001(最小生成树+路径最大值)
一个图上,有n个点,m条边,其中一些点是补给站,可以将主人公的背包充满,走在路上会消化边权值的食物,q次询问,问从x点旅行到y点,最少要带多大的背包才能使得不会主人公不会受饿。
做法:
求出每个点到最近的补给站的距离dist[i],然后将所有边权修改为dist[i]+dist[j]+w,求一下最小生成树,对于每次询问x,y,答案即为树上的边权最大值。
#include<bits/stdc++.h> using namespace std; #define mem(x) memset(x,0,sizeof x) #define debug cout<<"debug\n" const int maxn=100005; int n; struct edge { int u,v,val; edge(int _u=0,int _v=0,int _val=0) { u=_u; v=_v; val=_val; } }e[4*maxn]; bool cmp(edge a,edge b) { return a.val<b.val; } vector<edge>G[maxn]; vector<edge>G2[maxn]; inline void addedge(int u,int v,int val) { G[u].push_back(edge(u,v,val)); G[v].push_back(edge(v,u,val)); } inline void addedge2(int u,int v,int val) { G2[u].push_back(edge(u,v,val)); G2[v].push_back(edge(v,u,val)); } typedef pair<int,int> p; int dist[maxn]; priority_queue<p,vector<p>,greater<p> >q; int a[maxn]; int find(int x) { if (x != a[x]) { a[x] = find(a[x]); } return a[x]; } bool merge(int u,int v) { u=find(u); v=find(v); if(u!=v) { a[u]=v; return true; } return false; } int b[maxn],cnt,f[maxn],d[maxn],siz[maxn],son[maxn],rk[maxn],top[maxn],tid[maxn]; void dfs1(int u,int fa,int depth) { f[u]=fa; d[u]=depth; siz[u]=1; for(int i=0; i<G2[u].size(); i++) { int v=G2[u][i].v; if(v==fa) continue; b[v]=G2[u][i].val; dfs1(v,u,depth+1); siz[u]+=siz[v]; if(siz[v]>siz[son[u]]) son[u]=v; } } void dfs2(int u,int t) { top[u]=t; tid[u]=++cnt; rk[cnt]=u; if(!son[u]) return; dfs2(son[u],t); for(int i=0; i<G2[u].size(); i++) { int v=G2[u][i].v; if(v!=son[u]&&v!=f[u]) dfs2(v,v); } } int tr[maxn*4]; void build(int o,int l,int r) { if(l==r) { tr[o]=b[rk[l]]; return; } int lson=o<<1,rson=lson|1; int m=(l+r)>>1; build(lson,l,m); build(rson,m+1,r); tr[o]=max(tr[lson],tr[rson]); } int query(int o,int l,int r,int ql,int qr)//区间查询 { int lson=o<<1,rson=lson|1; int m=(l+r)>>1; if(ql<=l&&qr>=r) return tr[o]; if(qr<=m) return query(lson,l,m,ql,qr); if(ql>m) return query(rson,m+1,r,ql,qr); return max(query(lson,l,m,ql,qr),query(rson,m+1,r,ql,qr)); } int ask(int x,int y) { int ans=-1; int fx=top[x],fy=top[y]; while(fx!=fy) { if(d[fx]>=d[fy]) { ans=max(ans,query(1,1,n,tid[fx],tid[x])); x=f[fx]; } else { ans=max(ans,query(1,1,n,tid[fy],tid[y])); y=f[fy]; } fx=top[x]; fy=top[y]; } if(tid[x]==tid[y]) return ans; if(tid[x]<tid[y]) ans=max(ans,query(1,1,n,tid[x]+1,tid[y])); else ans=max(ans,query(1,1,n,tid[y]+1,tid[x])); return ans; } int main() { //freopen("in.txt","r",stdin); int m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { a[i]=i; int mark; scanf("%d",&mark); if(mark) q.push(p(0,i)); else dist[i]=1e9; } for(int i=1; i<=m; i++) { int u,v,val; scanf("%d%d%d",&u,&v,&val); addedge(u,v,val); } while(!q.empty()) { p a=q.top(); q.pop(); int u=a.second; int d=a.first; if(dist[u]<d) continue; for(int i=0;i<G[u].size();i++) { int v=G[u][i].v; int dd=G[u][i].val; if(dist[v]>d+dd) { q.push(p(d+dd,v)); dist[v]=d+dd; } } } int cnt=0; for(int i=1;i<=n;i++) { for(int j=0;j<G[i].size();j++) { G[i][j].val+=dist[i]+dist[G[i][j].v]; e[++cnt]=edge(i,G[i][j].v,G[i][j].val); } } sort(e+1,e+cnt+1,cmp); for(int i=1;i<=cnt;i++) { int x=e[i].u; int y=e[i].v; if(merge(x,y)) addedge2(x,y,e[i].val); } cnt=0; dfs1(1,0,1); dfs2(1,1); build(1,1,n); int Q; scanf("%d",&Q); while(Q--) { int x,y; scanf("%d%d",&x,&y); printf("%d\n",ask(x,y)); } return 0; }
J - Distance ZOJ - 4003 (思维)
给定两个数列a,b,分别在两个数列上选取相同长度的连续的子序列,使得两个子序列中Σ(abs(a[i]-b[j])^p)<=v,问有多少种选取方案
首先考虑暴力去枚举两个子序列的起始位置i,j,然后从i和j出发一个一个的求和直到sum>v,就得到了从i,j起头的可行方案,然后将所有的方案加起来就是答案,复杂度O(n^3)
思考:
如果a[1...5]与b[2...6]是求出来的最长可行方案,那么对于2,3起头的时候,a[2...5]与b[3...6]也是可行的,如此,便不需要再从2和3开始跑。
于是做出以下优化:
在每次暴力的时候记录i,j对应的尾位置
那么对于在之前已经做过求和的一对数字a[tmp_i]和b[tmp_j],获取上一次计算这一段时的尾位置,从尾位置开始往后延申,跑到sum>v为止,记录尾位置
复杂度O(n^2)
需要注意的细节:请手写求幂,请手写求幂,请手写求幂(重要的事情说三遍),用库里的pow函数会tle(大常数)
#include<bits/stdc++.h> using namespace std; #define ll long long #define mem(x) memset(x,0,sizeof x) #define debug cout<<"debug\n" const int maxn=1005; ll a[maxn],b[maxn]; int tail[maxn][maxn]; ll sum[maxn][maxn]; ll n,v,p; inline ll f(int i,int j) { ll ans=1; ll x=abs(a[i]-b[j]); for(int i=0;i<p;i++) ans*=x; return ans; } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int t; scanf("%d",&t); while(t--) { scanf("%lld%lld%lld",&n,&v,&p); for(int i=1; i<=n; i++) scanf("%lld",a+i); for(int i=1; i<=n; i++) scanf("%lld",b+i); for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { ll tmp=sum[i-1][j-1]-f(i-1,j-1); int k; for(k=tail[i-1][j-1]-1; i+k<=n&&j+k<=n; k++) { if(tmp+f(i+k,j+k)<=v) tmp+=f(i+k,j+k); else break; } sum[i][j]=tmp; tail[i][j]=k; } } ll ans=0; for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { ans+=tail[i][j]; tail[i][j]=sum[i][j]=0; } } printf("%lld\n",ans); } return 0; }