Codeforces 部分题目题解(口胡)

883D

题面

题目大意:给你一个长度为n的字符串,上面有牛(“P”),草(“*”)和空地(“.”)。现在你给每一头牛规定一个方向,它会一直往前吃草,直到走到边界。每一份草只会被吃1次,要求输出最多吃多少草,以及在此基础下吃完最后一份草的最小时间。n<=1000000。

做法:很明显两头牛就可以吃完所有草,于是暴力处理0,1头牛的情况。然后由于具有单调性,考虑二分答案后贪心(时限3s不虚)。接下来证明两个小结论:

1.最前面的草,顶多会被它后面第二头牛吃掉。
这个从上图就可以看出。①中红色和蓝色的边分别比②中红色和蓝色的边长。

2.二分完阀值mid后,如果最前面的草后面mid处至少有两头牛,且第一头牛与第二头牛之间没有草,那么让第一头牛吃该草,第二头牛往后吃。
这个结论应该很显然……
但是如果两头牛中间有草怎么办呢?是不是让第二头牛往前吃,第一头牛往后吃就最优了?答案是否定的。我一开始按照这个思路写了个贪心,结果被下面这组数据卡掉了:
19
P.....P...P
(这组数据的最优方案应该是让第一,三头牛往前吃,第二头牛往后吃)
所以我们要用DP!记f[i]=j表示考虑完第i头牛之后,最多能处理完1~j处的草。然后根据上述思路用f[i-2]或f[i-1]转移即可。
我一开始把时限看成了1s,结果想了很久都想不出O(n)的做法QAQ

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1000100;

int sum[maxn];
int f[maxn];
int id[maxn];

char s[maxn];
int a[maxn];

int n;
int cnt=0,num=0;

bool Judge(int x)
{
    f[0]=0;
    for (int i=1; i<=num; i++)
    {
        f[i]=0;
        int y=id[i];
        if (f[i-1]>=y-1) f[i]=max(f[i],y+x);
        if (f[i-1]<y-1)
            if (sum[y]-sum[ f[i-1] ])
                if (sum[max(0,y-x-1)]-sum[ f[i-1] ]>0) return false;
                else
                {
                    f[i]=max(f[i],y);
                    if ( i>=2 && sum[max(0,y-x-1)]-sum[ f[i-2] ]<=0 )
                        f[i]=max(f[i],id[i-1]+x);
                }
            else f[i]=max(f[i],y+x);
    }
    if ( f[num]>=n ||  sum[n]-sum[ f[num] ]<=0 ) return true;
    return false;
}

int Binary()
{
    int L=0,R=n;
    while (L+1<R)
    {
        int mid=(L+R)>>1;
        if ( Judge(mid) ) R=mid;
        else L=mid;
    }
    return R;
}

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

    scanf("%d",&n);
    scanf("%s",s);
    for (int i=1; i<=n; i++)
    {
        if (s[i-1]=='*') a[i]=0,cnt++;
        if (s[i-1]=='P') a[i]=1,id[++num]=i;
        if (s[i-1]=='.') a[i]=2;
    }
    if (num<=1)
        if (num==0) printf("0 0\n");
        else
        {
            int x=0,y=0;
            for (int i=1; i<=n; i++)
                if (a[i]==1) x=i;
            for (int i=1; i<=x; i++)
                if (a[i]==0) y++;

            int Le=0,Ri=0;
            for (int i=x; i>=1; i--)
                if (a[i]==0) Le=x-i;
            for (int i=x; i<=n; i++)
                if (a[i]==0) Ri=i-x;

            if (y>cnt-y) printf("%d %d\n",y,Le);
            else
                if (y<cnt-y) printf("%d %d\n",cnt-y,Ri);
                else
                    if (Le<Ri) printf("%d %d\n",y,Le);
                    else printf("%d %d\n",cnt-y,Ri);
        }
    else
    {
        sum[0]=0;
        for (int i=1; i<=n; i++)
        {
            sum[i]=sum[i-1];
            if (a[i]==0) sum[i]++;
        }
        int ans=Binary();
        printf("%d %d\n",cnt,ans);
    }

    return 0;
}

875E

题面

题目大意:有n个城市,给出它们在数轴上的坐标。现在有两个人在s1和s2处,他们要按顺序走完这n个城市,求他们两个人最大距离的最小值。n<=100000。

做法:分析之后发现,它就是要你把这n个城市分成若干段,使得每一段的所有城市到上一段的最后一个城市的距离小于等于ans。二分答案之后用treap维护即可,时间复杂度 O(nlog2(n))
然而这样做会被卡常(虽然CF的机子跑得灰常快)。
更优的方法是从后往前考虑。如果第n-1个城市在[a[n]-mid,a[n]+mid]的范围内,就可以无视掉第n个城市;否则就要求第n-2个城市在[a[n]-mid,a[n]+mid]与[a[n-1]-mid,a[n-1]+mid]的交集内,不存在则无解。这样时间就是 O(nlog(n))

CODE(Treap):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=100100;
const int oo=2000001000;

const long long M1=998244353;
const long long M2=1000000007;
const long long M3=1333333331;
typedef long long LL;
LL seed;

struct Tnode
{
    int val,id,min_id,fix;
    Tnode *lson,*rson;
} tree[maxn];
Tnode *Root;
int cur;

int Right[maxn];
int a[maxn];
int n,s1,s2;

int Rand()
{
    seed=(seed*M1+M2)%M3;
    return seed;
}

Tnode *New_node(int Val,int Id)
{
    cur++;
    tree[cur].val=Val;
    tree[cur].min_id=tree[cur].id=Id;
    tree[cur].fix=Rand();
    tree[cur].lson=tree[cur].rson=NULL;
    return tree+cur;
}

void Recount(Tnode *P)
{
    P->min_id=P->id;
    if (P->lson) P->min_id=min(P->min_id,P->lson->min_id);
    if (P->rson) P->min_id=min(P->min_id,P->rson->min_id);
}

void Right_turn(Tnode *&P)
{
    Tnode *W=P->lson;
    P->lson=W->rson;
    W->rson=P;
    P=W;
    Recount(P->rson);
    Recount(P);
}

void Left_turn(Tnode *&P)
{
    Tnode *W=P->rson;
    P->rson=W->lson;
    W->lson=P;
    P=W;
    Recount(P->lson);
    Recount(P);
}

void Insert(Tnode *&P,int Val,int Id)
{
    if (!P) P=New_node(Val,Id);
    else
        if ( Val<P->val || ( Val==P->val && Id<P->id ) )
        {
            Insert(P->lson,Val,Id);
            if ( P->lson->fix < P->fix ) Right_turn(P);
            else Recount(P);
        }
        else
        {
            Insert(P->rson,Val,Id);
            if ( P->rson->fix < P->fix ) Left_turn(P);
            else Recount(P);
        }
}

int Find_succ(Tnode *P,int Val,int Ans)
{
    if (!P) return Ans;
    if (Val<P->val)
    {
        Ans=min(Ans,P->id);
        if (P->rson) Ans=min(Ans,P->rson->min_id);
        return Find_succ(P->lson,Val,Ans);
    }
    return Find_succ(P->rson,Val,Ans);
}

int Find_prev(Tnode *P,int Val,int Ans)
{
    if (!P) return Ans;
    if (P->val<Val)
    {
        Ans=min(Ans,P->id);
        if (P->lson) Ans=min(Ans,P->lson->min_id);
        return Find_prev(P->rson,Val,Ans);
    }
    return Find_prev(P->lson,Val,Ans);
}

bool Judge(int x)
{
    Root=NULL;
    cur=-1;
    Insert(Root,-oo,n+1);
    Insert(Root,oo,n+1);
    for (int i=n; i>=1; i--)
    {
        int Succ=Find_succ(Root,a[i]+x,n+1);
        int Prev=Find_prev(Root,a[i]-x,n+1);
        Right[i]=min(Prev,Succ)-1;
        Insert(Root,a[i],i);
    }
    int now=0;
    int Succ=Find_succ(Root,s1+x,n+1);
    int Prev=Find_prev(Root,s1-x,n+1);
    now=max(now, min(Prev,Succ)-1 );
    Succ=Find_succ(Root,s2+x,n+1);
    Prev=Find_prev(Root,s2-x,n+1);
    now=max(now, min(Prev,Succ)-1 );
    for (int i=1; i<=n; i++)
    {
        if (i>now) return false;
        now=max(now,Right[i]);
    }
    return true;
}

int Binary()
{
    int L=0,R=1000000000;
    while (L+1<R)
    {
        int mid=(L+R)>>1;
        if ( Judge(mid) ) R=mid;
        else L=mid;
    }
    return R;
}

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

    scanf("%d%d%d",&n,&s1,&s2);
    seed=n;
    for (int i=1; i<=n; i++) scanf("%d",&a[i]);
    int ans=Binary();
    ans=max(ans, max(s1-s2,s2-s1) );
    printf("%d\n",ans);

    return 0;
}

CODE(区间交):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=100100;

int a[maxn];
int n,s1,s2;

bool Judge(int x)
{
    int L=a[n]-x,R=a[n]+x;
    for (int i=n-1; i>=1; i--)
    {
        if ( L<=a[i] && a[i]<=R ) L=a[i]-x,R=a[i]+x;
        else
        {
            L=max(L,a[i]-x);
            R=min(R,a[i]+x);
            if (L>R) return false;
        }
    }
    if ( L<=s1 && s1<=R ) return true;
    if ( L<=s2 && s2<=R ) return true;
    return false;
}

int Binary()
{
    int L=0,R=1000000000;
    while (L+1<R)
    {
        int mid=(L+R)>>1;
        if ( Judge(mid) ) R=mid;
        else L=mid;
    }
    return R;
}

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

    scanf("%d%d%d",&n,&s1,&s2);
    for (int i=1; i<=n; i++) scanf("%d",&a[i]);
    int ans=Binary();
    ans=max(ans, max(s1-s2,s2-s1) );
    printf("%d\n",ans);

    return 0;
}

875F

题面

题目大意:给出m个三元组(a,b,c),表示如果该组选择了a或b两个数中的一个,你就会获得c的报酬。每个数顶多属于一个组,每个组顶多选择一个数。要求最大化报酬和。a,b<=n,n,m<=200000。

做法:这就是道典型的二分图匹配嘛
将所有三元组按c从大到小排序,然后按顺序处理。对于第i个三元组,先查看ai和bi是否在同一个集合,是的话再看这个集合是否已经有一个环,有环则选不了;不在一个集合,就看两个集合是否都有环,都有环则选不了,否则获得c的贡献,然后合并ai和bi所在集合。时间复杂度为 O(nα(n))

(代码因特殊原因不贴出)。
lhxQAQ老贼丧天良,我与珂朵莉共存亡!!!


891C

题面

题目大意:先给出一幅n个点,m条边的图(边按输入顺序编号)。然后有q个询问,每次询问给出数字k,再给出k条边的编号,问这k条边能不能同时存在于这幅图的最小生成树上。可以则输出”YES”,否则输出”NO”(均不含引号)。 n,m,q,k<=5105

做法:一开始写了个树上倍增,想着随便构一棵最小生成树,然后看路径最大值是否等于当前边权就可以了。然而这样连样例都过不了,因为同一个询问的边有可能相互冲突……
正确的方法是将所有边离线,按权值从小到大排序,权值相同则按所在询问的编号从小到大。要知道属于第i个询问,权值为val的边是否能存在于最小生成树上,就要将权值为1~val-1的边加进并查集里,并将其它权值为val的,属于第i个询问的边加进并查集,然后看两点是否连通。如果下一条边和当前边权值相同但不属于同一个询问,为了保证正确性,需要将权值为val的,属于第i个询问的边先退出并查集。时间复杂度 O(nlog(n))

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=500100;

struct edge
{
    int u,v,w;
} e[maxn];

struct data
{
    int a,b,val,id;
} ask[maxn];
int cur=0;

int fa[maxn];
int Size[maxn];

int sak[maxn];
int tail=0;

bool ans[maxn];
int n,m,q;

bool Comp1(edge x,edge y)
{
    return x.w<y.w;
}

bool Comp2(data x,data y)
{
    return x.val<y.val || ( x.val==y.val && x.id<y.id );
}

int Find(int x)
{
    if (fa[x]==x) return x;
    return Find(fa[x]);
}

void Add(int x,int y)
{
    x=Find(x);
    y=Find(y);
    if (x==y) return;
    if (Size[x]<Size[y]) swap(x,y);
    fa[y]=x;
    Size[x]+=Size[y];
}

void Clear()
{
    while (tail)
    {
        int x=sak[tail];
        Size[ fa[x] ]-=Size[x];
        fa[x]=x;
        tail--;
    }
}

void Push(int x,int y)
{
    if (Size[x]<Size[y]) swap(x,y);
    fa[y]=x;
    Size[x]+=Size[y];
    sak[++tail]=y;
}

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

    scanf("%d%d",&n,&m);
    for (int i=1; i<=m; i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    scanf("%d",&q);
    for (int i=1; i<=q; i++)
    {
        int k;
        scanf("%d",&k);
        for (int j=1; j<=k; j++)
        {
            int x;
            scanf("%d",&x);
            cur++;
            ask[cur].a=e[x].u;
            ask[cur].b=e[x].v;
            ask[cur].val=e[x].w;
            ask[cur].id=i;
        }
        ans[i]=1;
    }

    sort(e+1,e+m+1,Comp1);
    sort(ask+1,ask+cur+1,Comp2);
    for (int i=1; i<=n; i++) fa[i]=i,Size[i]=1;
    int h1=1,h2=1;
    while (h2<=cur)
    {
        Clear();
        while ( e[h1].w<ask[h2].val && h1<=m ) Add(e[h1].u,e[h1].v),h1++;
        int t2=h2;
        while ( t2<=cur && ask[t2].val==ask[h2].val )
        {
            if (ask[t2].id!=ask[t2-1].id) Clear();
            int x=Find(ask[t2].a);
            int y=Find(ask[t2].b);
            if (x==y) ans[ ask[t2].id ]=0;
            else Push(x,y);
            t2++;
        }
        h2=t2;
    }
    for (int i=1; i<=q; i++)
        if (ans[i]) printf("YES\n");
        else printf("NO\n");

    return 0;
}

891B

题面

题目大意:给出一个长度为n的序列a,它有 2n2 个非空真子集。现要求将a中的数打乱,构造出序列b,使得b的这些子集的对应位置之和与a的对应位置之和都不等。n<=22。a中的数互不相同。

做法:一道有点脑洞的构造题。
将a中的数从小到大排序,然后全体右移一位,最小的数变成最大。这样不包含原先最小数的集合,每个的和都变小了;包含了原先最小数的集合,考虑其补集,补集的和一定变小,所以该集合之和一定变大。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=30;

struct data
{
    int val,id;
} a[maxn];
int n;

bool Comp1(data x,data y)
{
    return x.val<y.val;
}

bool Comp2(data x,data y)
{
    return x.id<y.id;
}

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

    scanf("%d",&n);
    for (int i=1; i<=n; i++) scanf("%d",&a[i].val),a[i].id=i;
    sort(a+1,a+n+1,Comp1);
    a[n+1].val=a[1].val;
    for (int i=1; i<=n; i++) a[i].val=a[i+1].val;
    sort(a+1,a+n+1,Comp2);
    for (int i=1; i<=n; i++) printf("%d ",a[i]);
    printf("\n");

    return 0;
}

883B

题面

题目大意:有n名军人,军衔等级从低到高为1到k中的整数。给你m对关系(xi,yi),表示要满足军人xi的军衔高于军人yi的军衔。已知其中一些军人的军衔,求出任意一种所有军人可能的军衔的方案,并且每种军衔都至少存在一名军人,如无法满足条件输出“-1”。一开始给定数组r,r[i]不为0表示已知第i位军人的军衔为r[i]。 n,m,k<=2105

做法:一开始想错了贪心,导致WA了好多发,最后看了DLee大佬的代码才明白。
先将大小关系构出一个图,如果有环则无解。然后正反向拓扑一遍就可以知道每一个人军衔的下限Min与上限Max。现在的问题就变成了如何用这些区间覆盖1~k。我们从小到大考虑下限。假设当前做到i,则用一个堆维护所有Min<=i的区间的Max,每一次取出一个Max最小的来覆盖i。但如果堆里最小的Max都等于i,则令其答案一定要等于i。
接下来我们发现一个点的答案确定了之后,它所到达的点的Min会改变(变为当前点的答案+1)。于是干脆一开始先不要算出Min,边做边算,用一个链表存Min为某个值的人有哪些即可。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=200100;

struct edge
{
    int obj;
    edge *Next;
} e[maxn*3];
edge *head[maxn];
edge *nhead[maxn];
int cur=-1;

int Heap[maxn];
int tail=0;

int Min[maxn];
int Max[maxn];
int ans[maxn];

int pin[maxn];
int pout[maxn];

int vis[maxn];
int que[maxn];
int he,ta;

int r[maxn];
int n,m,k;

void Add(edge **Head,int x,int y)
{
    cur++;
    e[cur].obj=y;
    e[cur].Next=Head[x];
    Head[x]=e+cur;
}

void Dfs(int node)
{
    vis[node]=1;
    for (edge *p=head[node]; p; p=p->Next)
    {
        int to=p->obj;
        if (vis[to]==1)
        {
            printf("-1\n");
            exit(0);
        }
        if (!vis[to]) Dfs(to);
    }
    vis[node]=2;
}

void Calc_Max()
{
    he=0,ta=1;
    for (int i=1; i<=n; i++)
    {
        Max[i]=k;
        if (!pout[i]) que[++ta]=i;
        if (r[i]) Max[i]=r[i];
    }
    while (he<ta)
    {
        int node=que[++he];
        for (edge *p=nhead[node]; p; p=p->Next)
        {
            int to=p->obj;
            Max[to]=min(Max[to],Max[node]-1);
            pout[to]--;
            if (!pout[to])
            {
                que[++ta]=to;
                if ( r[to]>Max[to] && r[to] )
                {
                    printf("-1\n");
                    exit(0);
                }
                if (r[to]) Max[to]=r[to];
            }
        }
    }
}

void Insert(int x)
{
    Heap[++tail]=x;
    x=tail;
    while (x>1)
    {
        int y=x>>1;
        if ( Max[ Heap[y] ]<Max[ Heap[x] ] ) break;
        swap(Heap[x],Heap[y]);
        x=y;
    }
}

int Delete()
{
    int temp=Heap[1];
    Heap[1]=Heap[tail];
    tail--;
    int x=1;
    while (1)
    {
        int y=x,Left=x<<1,Right=Left|1;
        if ( Left<=tail && Max[ Heap[Left] ]<Max[ Heap[y] ] ) y=Left;
        if ( Right<=tail && Max[ Heap[Right] ]<Max[ Heap[y] ] ) y=Right;
        if (x==y) break;
        swap(Heap[x],Heap[y]);
        x=y;
    }
    return temp;
}

void Update(int node)
{
    for (edge *p=head[node]; p; p=p->Next)
    {
        int to=p->obj;
        pin[to]--;
        Min[to]=max(Min[to],ans[node]+1);
        if (!pin[to])
        {
            if ( r[to]<Min[to] && r[to] )
            {
                printf("-1\n");
                exit(0);
            }
            if (r[to]) Min[to]=r[to];
            Add(nhead,Min[to],to);
        }
    }
}

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

    scanf("%d%d%d",&n,&m,&k);
    for (int i=1; i<=n; i++) scanf("%d",&r[i]),head[i]=nhead[i]=NULL;
    for (int i=1; i<=m; i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        Add(head,y,x);
        pin[x]++;
        pout[y]++;
        Add(nhead,x,y);
    }

    for (int i=1; i<=n; i++)
        if (!vis[i]) Dfs(i);
    Calc_Max();
    for (int i=1; i<=n; i++)
        if (Max[i]<1)
        {
            printf("-1\n");
            return 0;
        }

    for (int i=1; i<=k; i++) nhead[i]=NULL;
    for (int i=1; i<=n; i++)
        if (!pin[i])
        {
            Min[i]=1;
            if (r[i]) Min[i]=r[i];
            Add(nhead,Min[i],i);
        }
    for (int i=1; i<=k; i++)
    {
        for (edge *p=nhead[i]; p; p=p->Next) Insert(p->obj);
        if (!tail)
        {
            printf("-1\n");
            return 0;
        }
        int x=Delete();
        ans[x]=i;
        Update(x);
        while ( tail && Max[ Heap[1] ]==i )
            x=Delete(),ans[x]=i,Update(x);
    }
    for (int i=1; i<=n; i++)
        if (!ans[i])
        {
            printf("-1\n");
            return 0;
        }

    for (int i=1; i<=n; i++) printf("%d ",ans[i]);
    printf("\n");

    return 0;
}

(持续待更)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值