1. 程式人生 > >Codeforces 部分題目題解(口胡)

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

(持續待更)