• 2021暑期cf加训1


    比赛链接:https://codeforces.com/group/2g1PZcsgml/contest/337661

    A,D,K,3/12,第11名,还好。

    A他们挺快写出来了。D我感觉可以用上一场A一样的做法,成功。G用AC自动机把K过了;字符串的板子我还都老生疏了,真惭愧。其他题看来看去都没有成熟的思路,直到比赛结束。G最后十五分钟用分治猛改J题,可惜太匆忙,WA了;后来改改就A了。

    话说那个教室空调好冷啊。

    C:Cover the Paths

    题意:

    给定一颗树和树上的若干条路径(的两个端点),求最小的点集,使得每条路径至少有一个点在此点集中。( 1 leq n , m leq 10^5 ) 。

    分析:

    树上的路径比较特殊,一般要关注LCA,这题也是。一个很好的发现是两条路径如果相交,那么较低路径上的LCA一定是交点之一。所以答案一定从LCA中选。就可以贪心,从低往高找LCA,用找到的LCA覆盖尽量多的路径。

    但怎么实现还是个问题;这里可以用set,用法好妙啊。给每个点开一个set,一开始存的是以这个点为端点的路径的编号。然后dfs,把子节点的set启发式合并到父节点的set里;如果合并过程中发现有相同的元素,说明这个父节点是个LCA(两端点在此会师),那么答案++,set全清空,意思是这些路径都被覆盖了。可以想到,如果一条路径在其某一侧被覆盖了,两端点中那一侧的那个的set里一开始存的路径编号就被清空了,那么在LCA处也不会会师,也就不再算答案了。复杂度是 ( O(nlog^2(n)) ) 的。

    代码如下:

    #include<iostream>
    #include<set>
    using namespace std;
    int const N=1e5+5;
    int n,m,hd[N],to[N<<1],nxt[N<<1],cnt,id[N],ans;
    bool is[N];
    set<int>st[N];
    int rd()
    {
        int ret=0,f=1; char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
        while(c>='0'&&c<='9')ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
        return ret*f;
    }
    void add(int x,int y){nxt[++cnt]=hd[x]; hd[x]=cnt; to[cnt]=y;}
    int mer(int x,int y)
    {
        if(st[x].size()<st[y].size())swap(x,y);
        for(int it:st[y])
        {
            if(st[x].find(it)!=st[x].end())return -1;
            st[x].insert(it);
        }
        return x;
    }
    void dfs(int u,int fa)
    {
        if(is[u])ans++,st[id[u]].clear();//
        for(int i=hd[u],v;i;i=nxt[i])
        {
            if((v=to[i])==fa)continue;
            dfs(v,u);
            if(is[u]){st[v].clear(); continue;}//前面已经决定选u了
            int k=mer(id[u],id[v]);
            if(k==-1)st[id[u]].clear(),st[id[v]].clear(),is[u]=1,ans++;
            else id[u]=k;
        }
    }
    int main()
    {
        n=rd();
        for(int i=1,x,y;i<n;i++)
            x=rd(),y=rd(),add(x,y),add(y,x);
        for(int i=1;i<=n;i++)id[i]=i;
        m=rd();
        for(int i=1,x,y;i<=m;i++)
        {
            x=rd(); y=rd();
            if(x==y)is[x]=1;
            else st[x].insert(i),st[y].insert(i);
        }
        dfs(1,0);
        printf("%d
    ",ans);
        for(int i=1;i<=n;i++)
            if(is[i])printf("%d ",i);
        printf("
    ");
        return 0;
    }
    me

    J:Subsequence Sum Queries

    题意:

    给定一个长度为 ( n ) 的序列 ( a ) ,( m ) 次询问,每次问 ( l , r ) 间有多少子序列的和 ( %m ) 为 ( 0 ) 。当然,子序列可以不连续。 ( 1 leq n leq 2*10^5 , 1 leq m leq 20 ) 。

    分析:

    分治,巧妙的是边分治边处理那 ( m ) 个询问;也就是离线做。

    对于每一段,分成左右两边,用 ( fl[i][k] , fr[j][k] ) 分别记录左边某处 ( i ) 到 ( mid ) 之间、和为 ( k ) 的方案数,右边类似;

    分治到( (l , r ) ) 时带一个vector记录在这个区间内的询问,求完 ( f ) 后遍历vector,如果横跨两边,就计算两边合起来的答案数;否则放进下一层的vector里继续递归处理。

    复杂度不大于 ( O(nlogn) ) 。 妙啊。

    代码如下:

    #include<iostream>
    #include<vector>
    #define ll long long
    #define pb push_back
    using namespace std;
    int const N=2e5+5,md=1e9+7;
    int n,a[N],m,nq;
    ll fl[N][20],fr[N][20],ans[N];
    struct Nd{
        int l,r,id;
    }q[N];
    void work(int l,int r,vector<Nd>v)
    {
        if(l>r||v.size()==0)return;//
        if(l==r)
        {
            int pls=(a[l]==0)?2:1,sz=v.size();
            for(int i=0;i<sz;i++)ans[v[i].id]=pls;
            return;
        }
        int mid=((l+r)>>1);
        for(int i=l;i<=r;i++)
            for(int j=0;j<20;j++)fl[i][j]=0,fr[i][j]=0;
        for(int i=1;i<20;i++)fl[mid+1][i]=0;
        fl[mid+1][0]=1;
        for(int i=mid;i>=l;i--)
            for(int j=0;j<m;j++)
                fl[i][j]=(fl[i+1][j]+fl[i+1][(j-a[i]+m)%m])%md;
        for(int i=1;i<20;i++)fr[mid][i]=0;
        fr[mid][0]=1;
        for(int i=mid+1;i<=r;i++)
            for(int j=0;j<m;j++)
                fr[i][j]=(fr[i-1][j]+fr[i-1][(j-a[i]+m)%m])%md;
        //printf("l=%d r=%d
    ",l,r);
        vector<Nd>vl,vr; int sz=v.size();
        for(int i=0;i<sz;i++)
        {
            int id=v[i].id,L=v[i].l,R=v[i].r;
            if(R<=mid)vl.pb(v[i]);
            else if(L>=mid+1)vr.pb(v[i]);
            else
            {
                //printf("ans[%d]=%d
    ",id,ans[id]);
                for(int j=0;j<m;j++)
                    ans[id]=(ans[id]+fl[L][j]*fr[R][(m-j)%m]%md)%md;
                //printf("ans[%d]=%d
    ",id,ans[id]);
            }
        }
        work(l,mid,vl); work(mid+1,r,vr);
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]),a[i]%=m;
        scanf("%d",&nq); vector<Nd>v;
        for(int i=1;i<=nq;i++)
            scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i,v.pb(q[i]);
        work(1,n,v);
        for(int i=1;i<=nq;i++)printf("%lld
    ",ans[i]);
        return 0;
    }
    me

    K:Consistent Occurrences

    题意:

    给一个长度为 ( n ) 字符串 ( s ) , ( m ) 次询问,每次问一个字符串 ( t ) 在 ( s ) 中不相交地出现了几次。( 1 leq n,m leq 10^5 ) , 所有 ( t ) 的长度之和不超过 ( 10^5 ) 。

    分析:

    首先,可以分长度 ( leq n ) 的询问串和长度 ( > n ) 的询问串;

    对于较长的询问串,直接哈希查找,次数不超过 ( sqrt{n} ) 次;

    对于所有较短的询问串,把它们一起建一个字典树;在字典树的点上开vector记录以此点为结尾的询问串的编号,再记录一个 ( lst[u] )表示之后匹配过程中最后出现的位置;然后遍历 ( s ) 的每个位置作为起点,查询长度不超过 ( sqrt{n} ) 的一段在字典树上的匹配情况,经过的更新一下次数和 ( lst[u] ) 。

    然后把字典树上点记录的次数再加到对应的询问串上,就做完了。

    时间复杂度是 ( O(nsqrt{n}) ) 。

    代码如下:

    #include<iostream>
    #include<vector>
    #include<cstring>
    #include<cmath>
    #define ll long long
    #define pb push_back
    using namespace std;
    int const N=1e5+5,bs=131,md=1e9+7;
    int n,m,ch[N][26],cnt,ans[N],lst[N],num[N];
    ll hs[N],pw[N];
    char s[N],t[N];
    vector<int>v[N];
    void init()
    {
        hs[0]=s[0]; pw[0]=1;
        for(int i=1;i<n;i++)hs[i]=(hs[i-1]*bs%md+s[i])%md;
        for(int i=1;i<=n;i++)pw[i]=(pw[i-1]*bs)%md;
    }
    void add(int id,char *t)
    {
        int u=0,len=strlen(t);
        for(int i=0;i<len;i++)
        {
            int x=t[i]-'a';
            if(!ch[u][x])ch[u][x]=++cnt;
            u=ch[u][x];
        }
        v[u].pb(id); lst[u]=-1;
    }
    ll get(int l,int r){return ((hs[r]-hs[l-1]*pw[r-l+1]%md)%md+md)%md;}
    void run(int l,int r)
    {
        int u=0;
        for(int i=l;i<=r;i++)
        {
            int x=s[i]-'a';
            if(!ch[u][x])return;
            u=ch[u][x];
            if(v[u].size()&&l>lst[u])num[u]++,lst[u]=i;
        }
    }
    int main()
    {
        scanf("%d%d%s",&n,&m,&s);
        init(); int sl=sqrt(n);
        for(int i=1,len;i<=m;i++)
        {
            scanf("%s",&t); len=strlen(t);
            if(len<=sl)add(i,t);
            else
            {
                ll ths=t[0];
                for(int j=1;j<len;j++)ths=(ths*bs%md+t[j])%md;
                for(int j=0;j+len-1<n;j++)
                    if(ths==get(j,j+len-1))ans[i]++,j=j+len-1;
            }
        }
        //for(int i=0;i+sl-1<n;i++)run(i,i+sl-1);
        for(int i=0;i<n;i++)run(i,min(i+sl-1,n-1));
        for(int i=1;i<=cnt;i++)
            for(int it:v[i])ans[it]=num[i];
        for(int i=1;i<=m;i++)printf("%d
    ",ans[i]);
        return 0;
    }
    me

    L:Increasing Costs

    题意:

    给定一张图,可求1号点到每个点的最短路,问单独增大每条边,有多少点的最短路会受到影响?( 2 leq n leq 2*10^5 , n-1 leq m leq 2*10^5 ) 。

    分析:

    支配树?

    先dijkstra求出最短路;过程中每个点用vector存下相同的最短路,存最后那条边即可。然后这些最短路就形成了一个拓扑图。

    这时我们需要从这个图中提取出一棵树,使得一条边下的子树的所有点都会被这条边影响。想想可以发现,对于一个点,所有最短路上的前一个点(下称前驱点)在图中的拓扑序比它靠前;如果我们按照拓扑序构造那棵树,那么那些前驱点会在当前点之前加入树中。而对于这个点,可以影响到它最短路的边就是这些路共有的部分,也就是树上LCA往上的部分。

    所以我们找到所有前驱点在已有的树上的LCA,然后把当前点直接连在LCA下面,这个树就还满足我们的需求。

    这样做完以后得到一棵树,然后dfs求子树大小就可以得到边影响的点数了。这还需要我们在造树的过程中时刻记录边的编号。

    代码如下:

    #include<iostream>
    #include<queue>
    #include<vector>
    #include<cstring>
    #define ll long long
    using namespace std;
    int const N=2e5+5;
    int n,m,hd[N],to[N<<1],fr[N<<1],nxt[N<<1],cnt=1;//cnt=1
    int du[N],f[N][25],dep[N],tag[N],ans[N],siz[N];
    ll D[N],w[N<<1];//ll!
    bool vis[N];
    vector<int>ve[N],E[N],tr[N];
    struct Nd{
        ll dis,id;
        bool operator < (const Nd &a) const
        {return dis>a.dis;}
    };
    priority_queue<Nd>q;
    queue<int>tq;
    void add(int x,int y,int s){nxt[++cnt]=hd[x]; hd[x]=cnt; to[cnt]=y; fr[cnt]=x; w[cnt]=s;}
    void dij()
    {
        q.push((Nd){0,1});
        memset(D,0x3f3f,sizeof D); D[1]=0;
        while(q.size())
        {
            int u=q.top().id; q.pop();
            if(vis[u])continue; vis[u]=1;
            for(int i=hd[u],v;i;i=nxt[i])
            {
                if(vis[v=to[i]])continue;
                if(D[v]>D[u]+w[i])
                {
                    ve[v].clear(); ve[v].push_back(i); du[v]=1;//ve:拓扑图上指来的边
                    D[v]=D[u]+w[i];
                    q.push((Nd){D[v],v});
                }
                else if(D[v]==D[u]+w[i])ve[v].push_back(i),du[v]++;
            }
        }
        for(int i=1;i<=n;i++)
            for(int it:ve[i])E[fr[it]].push_back(it);//E:拓扑图上指出的边
        //for(int i=1;i<=n;i++)printf("du[%d]=%d
    ",i,du[i]);
    }
    int getlca(int x,int y)
    {
        if(dep[x]<dep[y])swap(x,y);
        for(int i=20;i>=0;i--)
            if(f[x][i]&&dep[f[x][i]]>=dep[y])x=f[x][i];
        if(x==y)return x;
        for(int i=20;i>=0;i--)
            if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
        return f[x][0];
    }
    void topo()
    {
        tq.push(1);
        while(tq.size())
        {
            int u=tq.front(); tq.pop();
            for(int e:E[u])
            {
                int v=to[e]; du[v]--;
                //printf("%d-%d
    ",u,v);
                if(!du[v])tq.push(v);
                else continue;
                if(ve[v].size()==1)
                {
                    f[v][0]=u; dep[v]=dep[u]+1;
                    tag[v]=e/2;//边的编号/2
                    //printf("u=%d tag[%d]=%d
    ",u,v,tag[v]);
                    tr[u].push_back(v);//tr:最短路树上指向的点
                }
                else
                {
                    int lca=fr[ve[v][0]],sz=ve[v].size();
                    for(int i=1;i<sz;i++)
                        lca=getlca(lca,fr[ve[v][i]]);//所有最短路的lca
                    f[v][0]=lca; dep[v]=dep[lca]+1;
                    tr[lca].push_back(v);
                }
                for(int i=1;i<=20;i++)
                    f[v][i]=f[f[v][i-1]][i-1];
            }
        }
    }
    void dfs(int u)
    {
        siz[u]=1;
        for(int v:tr[u]){dfs(v); siz[u]+=siz[v];}
        if(tag[u])ans[tag[u]]=siz[u];
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1,x,y,s;i<=m;i++)
        {
            scanf("%d%d%d",&x,&y,&s);
            add(x,y,s); add(y,x,s);
        }
        dij(); topo(); dfs(1);
        //for(int i=1;i<=n;i++)printf("tag[%d]=%d
    ",i,tag[i]);
        for(int i=1;i<=m;i++)
            printf("%d
    ",ans[i]);
        return 0;
    }
    me
  • 相关阅读:
    奢想下财务自由吧--理想生活啊
    [翻译] AGG 之贝塞尔插值
    Beginning Silverlight 4 in C#Welcome to Silverlight 4[学习笔记]
    从属性赋值到MVVM模式详解
    Beginning Silverlight 4 in C#Silverlight的布局管理学习笔记
    Beginning Silverlight 4 in C#Silverlight工具包
    不用IDE写C#的Hello World
    NHibernate使用无状态Sessions
    Beginning Silverlight 4 in C#Silverlight控件
    NHibernate使用session.Merge[翻译]
  • 原文地址:https://www.cnblogs.com/Zinn/p/15046739.html
Copyright © 2020-2023  润新知