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;
}
View Code

 

 

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));
            }
        }
    }
}
View Code

 

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;
}
View Code

 

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;
}
View Code

 

 

posted @ 2018-10-25 21:01 Amori 阅读(...) 评论(...) 编辑 收藏