【CF】Codeforces Round #423(Div.1)

原题地址

A. String Reconstruction

【题目大意】
给定n个字符串在一个串T中出现的位置,求构造一个符合要求的字典序最小的T。

【解题思路】
暴力赋值肯定会T,重点是如何维护空位置。
我们DSU维护每个位置右边第一个空位置在哪,剩下的填a就行了。
我们还可以用线段树完成这个东西。
我们也可以用一个数组记录这个东西。
我们还可以用一些玄学加暴力搞这个东西。

【代码】

#include
const int MAXN = 2e6+5;
using namespace std;

int n,maxL;
char s[MAXN],ans[MAXN];

int main() 
{
//  freopen("A.in","r",stdin);
//  freopen("A.out","w",stdout);

    scanf("%d",&n);

    while (n--) 
    {
        int k;
        scanf("%s%d",s,&k);
        int len=strlen(s);
        int p,start = 0;
        for (int i=0;iscanf("%d",&p);
            start=max(start,p);
            for (int j=start;j1);
    }
    for(int i=1;i<=maxL;i++) 
    {
        if (ans[i]) printf("%c",ans[i]);
        else printf("a");
    }
    return 0;
}

B. High Load

【题目大意】
将n个节点构造成一棵树,树上有k个出口,使得距离最远的两个出口距离最小,出口为所有度为1的点。

【解题思路】
显然可以想到这是一颗多叉(菊花)树。
那么我们就可以把这颗树分的尽量多叉。

【代码】

#include
using namespace std;

int n,k;

int main()
{
//  freopen("B.in","r",stdin);
//  freopen("B.out","w",stdout);

    scanf("%d%d",&n,&k);
    n--;
    int len1=n/k;
    int len2=(n%k)?(len1+1):len1;
    int ans=(n%k>=2)?(len2*2):(len1+len2);
    printf("%d\n",ans);

    for(int i=2;i<=k;i++)
        printf("%d %d\n",1,i);
    for(int i=k+1;i<=n+1;i++)
        printf("%d %d\n",i-k,i);
    return 0;
}

C. DNA Evolution

【题目大意】
给定一个只包含A,T,C,G的字符串,有如下两种操作
1)修改一个点的字母
2)给定区间L, R和一个字符串e (strlen(e) <=10),组成一个足够区间长度的由若干个e重复组成的新串,eee…,问L,R区间中有几个位置对应的字母跟这个新的字符串对应的相同。

【解题思路】
e串长度最多为10,那么对于每一个字母最多有10种不同的起始位置,10个不同的长度进行重复。建立一个4*10*10的BST维护即可。

【代码】

#include
using namespace std;

const int MAXN=1e5+10;
int len,lena,ans,q;
int id[305],tree[13][13][6][MAXN];
char s[MAXN],a[13];

inline int lowbit(int x)
{
    return x&(-x);
}

inline void add(int x,int y,int z,int pos,int del)
{
    while(posx][y][z][pos]+=del;
        pos+=lowbit(pos);
    }
}

inline int query(int x,int y,int z,int pos)
{
    int ret=0;
    while(pos)
    {
        ret+=tree[x][y][z][pos];
        pos-=lowbit(pos);
    }
    return ret;
}

int main()
{
    freopen("C.in","r",stdin);
    freopen("C.out","w",stdout);

    id['A']=0;id['T']=1;id['C']=2;id['G']=3;

    scanf("%s",s);
    len=strlen(s);
    for(int i=1;i<=len;++i)
        for(int j=1;j<=10;++j)
            add(i%j,j,id[s[i-1]],i,1);

    scanf("%d",&q);
    while(q--)
    {
        int x,y,z;char ch[5];
        scanf("%d",&x);

        if(x==1)
        {
            scanf("%d%s",&y,ch);
            for(int i=1;i<=10;++i)
            {
                add(y%i,i,id[s[y-1]],y,-1);
                add(y%i,i,id[ch[0]],y,1);
            }
            s[y-1]=ch[0];
        }
        else
        {
            scanf("%d%d%s",&y,&z,a);
            lena=strlen(a);ans=0;

            for(int i=0;iy+i)%lena,lena,id[a[i]],z)-query((y+i)%lena,lena,id[a[i]],y-1);
            printf("%d\n",ans);
        }
    }

    return 0;
}

D. Best Edge Weight

【题目大意】
给定一幅无向图,问每一条边的边权最大为多少,可以被包含在一颗这幅图的最小生成树内。

【解题思路】
显然要先求MST
求完之后,分两种情况讨论:

1.若一条边不在生成树上,这条边肯定与生成树上的边共同构成了一个环。如果我们想用这条边替代环上的一条边,则权值最大必须小于环上的边的最大权值。
这个用LCA+倍增简单维护一下就好了。

2.若一条边在生成树上,每个不在生成树上的边和生成树构成的环,可以先预处理出所有两端在u到v路径上的不在树上的边的最小值。它的权值一定要小于最小值。
这个还是可以倍增一下。

【代码】

#include
using namespace std;

const int INF=1e9+10;
const int MAXN=4e5+10;
const int MAXP=20;

int n,m,cnt;
int fa[MAXN],ans[MAXN],dep[MAXN],head[MAXN],vs[MAXN];
int mx[MAXP+5][MAXN],anc[MAXP+5][MAXN];
bool bo[MAXN];

struct Tway
{ 
    int u,v,w,nex,id; 
};
Tway e[MAXN<<1],d[MAXN<<1];

inline bool cmp(Tway a,Tway b)
{ 
    return a.winline int findf(int x)
{ 
    return x==fa[x] ? x : fa[x]=findf(fa[x]);
}

inline void add(int u,int v,int w,int id)
{
    ++cnt;
    e[cnt].v=v;e[cnt].w=w;e[cnt].id=id;
    e[cnt].nex=head[u];head[u]=cnt; 
}

inline void dfs(int x,int f,int dis)
{
    dep[x]=dep[f]+1;anc[0][x]=f;mx[0][x]=dis;
    for(int i=1;i<=MAXP;++i)
    {
        anc[i][x]=anc[i-1][anc[i-1][x]];
        mx[i][x]=max(mx[i-1][anc[i-1][x]],mx[i-1][x]);
    }
    for(int i=head[x];i;i=e[i].nex)
        if(e[i].v!=f)
        {
            vs[e[i].v]=e[i].id;
            dfs(e[i].v,x,e[i].w);
        }
}

inline int lca( int x, int y, int &d )
{
    d=0; 
    if(dep[x]for(int i=MAXP;i>=0;--i) 
        if(dep[anc[i][x]]>=dep[y]) 
        {
            d=max(d,mx[i][x]); 
            x=anc[i][x];
        }
    if(x==y) 
        return x;
    for(int i =MAXP;i>=0;--i)
        if(anc[i][x]!=anc[i][y])
        {
            d=max(d,max(mx[i][x],mx[i][y]));
            x=anc[i][x];y=anc[i][y];
        }
    d=max(d,max(mx[0][x],mx[0][y]));
    return anc[0][x];
}

inline void solve(int x,int lca,int d)
{
    x=findf(x);
    while(dep[x]>dep[lca])
    {
        ans[vs[x]]=min(ans[vs[x]],d);
        int y=findf(anc[0][x]);
        fa[x]=y;
        x=findf(x);
    }
}

int main()
{
    freopen("D.in","r",stdin);
    freopen("D.out","w",stdout);

    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        fa[i]=i;
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d%d",&d[i].u,&d[i].v,&d[i].w);
        d[i].id=i;
    }
    sort(d+1,d+m+1,cmp);

    for(int i=1;i<=m;++i)
    {
        int fx=findf(d[i].u),fy=findf(d[i].v);
        if(fx!=fy)
        {
            bo[i]=true;fa[fx]=fy;
            add(d[i].u,d[i].v,d[i].w,d[i].id);
            add(d[i].v,d[i].u,d[i].w,d[i].id);
        }
    }

/*  for(int i=1;i<=n;++i)
    {
        printf("%d:",i);
        for(int j=head[i];j;j=e[j].nex)
            printf("%d ",e[j].v);
        printf("\n");
    }*/

    dfs(1,0,0);

    memset(ans,63,sizeof(ans));
    for(int i=1;i<=n;++i) 
        fa[i]=i;
    for(int i=1;i<=m;++i)
        if(!bo[i])
        {
            int u=d[i].u,v=d[i].v,f=lca(u,v,ans[d[i].id]);
            ans[d[i].id]--;
            solve(u,f,d[i].w-1); 
            solve(v,f,d[i].w-1);
        }
    for(int i=1;i<=m;++i) 
        if(ans[i]>=INF) 
            printf("-1 ");
        else 
            printf("%d ",ans[i]);

    return 0;
} 

E. Rusty String

【题目大意】
给定一个只包含通配符’?’和’v’,’K’的串,询问所有可能的循环节长度。

【解题思路】
首先个如果x是可能的循环节,那么2x,3x也一定是。
因此可以根据这个愉快地进行暴力
暴力check每一个长度,如果可行就把它的倍数都标为可行的。

当然正解是FFT。以下为看大佬博客整理

首先根据KMP的思想,如果存在长度为k的循环节那么存在长度为(n - k)的公共前后缀。
所以我们可以把这个串右移k位然后check,最后判一下特殊情况。

特殊情况是指类似于存在某一个i使得s[i] != s[i + 2k]并且s[i + k] == ‘?’。

首先可以初步地将一些循环节判断为不可行,对于看似没有问题的循环节长度,我们还需要check它的倍数中有没有被标记为不可行的,如果存在它就不可行(这样做的话就可以把以上的特殊情况处理掉)。

接下来是正常情况。

为了更快地进行check,所以,我们设A数组中A[i]为1当且仅当s[i] == ‘v’,B[i]为1当且仅当s[i] == ‘K’。

初步可行的条件是 nk1i=0(A[i]×B[i+k])=0 并且 nk1i=0(B[i]×A[i+k])=0

然后为了能够顺利地进行下一步,我们设A’[i] = A[n - i - 1]。于是你会发现两边A’的下标和B的和是一个定值,而且范围不相交。因此我们可以把A’数组和B数组当成两个多项式的系数数组,然后进行FFT。

当然我不会FFT所以没写。

【代码】

#include
using namespace std;

const int MAXN=5e5+5;
int ans,len,T;
char s[MAXN];
bool flag[5],bo[MAXN];

inline bool check(int l)
{
    for(int i=0;ichar c=s[i];
        for(int j=i+l;jif(c!='?' && s[j]!='?' && c!=s[j])
                return false;
            if(s[j]!='?')
                c=s[j];
        }
    }
    return true;
}

int main()
{
    freopen("E.in","r",stdin);
    freopen("E.out","w",stdout);

    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%s",&len,s);

        flag[0]=flag[1]=false;
        for(int i=0;iif(s[i]=='V')
                flag[0]=true;
            if(s[i]=='K')
                flag[1]=true;
        }

        if(!flag[0] && !flag[1])
        {
            printf("%d\n",len);
            for(int i=1;i<=len;++i)
                printf("%d ",i);
            printf("\n");
            continue;
        }

        ans=0;
        for(int i=1;i<=len;++i)
            if(!bo[i] && check(i))
                for(int j=i;j<=len;j+=i)
                {
                    ans+=bo[j]?0:1;
                    bo[j]=true;
                }
        printf("%d\n",ans);
        for(int i=1;i<=len;++i)
            if(bo[i])
            {
                printf("%d ",i);
                bo[i]=false;
            }
        printf("\n");
    }

    return 0;
}

F. Dirty Arkady’s Kitchen

【题目大意】
给一个无向图,其中的无向边有一些限定可以通行时间,人一开始在1号点,每一时刻他都需要不断移动,通过一条无向边的时间是1,问最早能在什么时刻到达 n 号点

【解题思路】
最简单的dp暴力是对于每一个点记录每个时间能否走到,然后转移,这样由于每条边进行了多次转移,显然会TLE。

观察到如果在第 i 秒来到点 S ,则第 i+2 秒也可以来到 S ,以此类推(当然不能超过范围)。那么可以考虑从奇偶性入手进行dp。

所以我们可以把奇偶性相同的一些时间点一起更新,将点分裂成奇数时刻和偶数时刻的点,把边拆成4条有向边:在偶数u->奇数v,奇数u->偶数v,v->u同理 。

更新的时候一次性把这条边能更新到的全部时间点都更新,就能使得边的更新数为m条 。

此时显然不能枚举时间去计算边的贡献,我们按照边产生贡献的时间顺序去计算贡献 。

为了更新每条边能贡献到的所有时刻,对于每条边需要求出一个 dp[i] 表示第一次到达这条边的时刻 。

对于一个点的所有能产生贡献的出边,他们产生贡献的顺序一定是按照出现时间升序的,对于每个点的出边按出现时刻升序排序。对每个点维护他的出现时间段 l[x] r[x]

那么我们可以:一开始从1的偶数点的第一条出边开始,每次判断当前这条边是否能产生贡献,尝试用它的 dpi 去更新这条边的结束点的时间段。

如果成功更新且结束点不在队列里,将结束点和他的当前弧放进队列,权为 (u,v) 出现后结束点当前弧出现的最早时间。

每次当前出发点的当前弧成功产生贡献或已经消失后,更新他的当前弧,将点和他新的当前弧放进队列,权也为这条边能够出现的最早时间。

【代码】

#include
using namespace std;

const int MAXN=5e5+5;
int n,m,ans;
int head[MAXN][2];

struct Tnode
{
    int v,l,r;
    Tnode(){}
    Tnode(int vv,int ll,int rr)
    {
        v=vv;l=ll;r=rr;
    }

    friend bool operator <(Tnode A,Tnode B)
    {
        return A.lvectorway[MAXN][2];

struct Tp
{
    int x,y;
    Tp(){}
    Tp(int xx,int yy)
    {
        x=xx;y=yy;
    }
    friend bool operator <(Tp A,Tp B)
    {
        return A.y>B.y;
    }
};
priority_queueq;


void init()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        int u,v,l,r;
        scanf("%d%d%d%d",&u,&v,&l,&r);
        for(int j=0;j<2;++j)
        {
            way[u][j].push_back(Tnode(v,l,r));
            way[v][j].push_back(Tnode(u,l,r));
        }
    }
    for(int i=1;i<=n;++i)
        for(int j=0;j<2;++j)
            sort(way[i][j].begin(),way[i][j].end());
}

int dij()
{
    int now,tim,xx,yy;
    q.push(Tp(1,0));

    while(!q.empty())
    {
        xx=q.top().x;yy=now=q.top().y;tim=yy&1;
        q.pop();

        if(xx==n)
            return yy;
        for(int j=head[xx][tim];jif(w.l>now)
                break;
            head[xx][tim]=j+1;
            if(w.rcontinue;
            now=max(now,w.r);
            if(now-tim&1)
                --now;
            int tmp=max(w.l,yy);
            if(tmp%2 != tim)//first time: (tmp&1 != tim)
                ++tmp;
            if(tmp+1<=w.r && tmp>=w.l)
                q.push(Tp(w.v,tmp+1));
        }
    }
    return -1;
}

int main()
{
    freopen("F.in","r",stdin);
    freopen("F.out","w",stdout);

    init();
    printf("%d\n",dij());

    return 0;
}

你可能感兴趣的:(codeforces)