1. 程式人生 > >[題解]牛客小白月賽6

[題解]牛客小白月賽6

題目比較基礎,但涵蓋的知識點很多,最後兩道題是賽後寫的。

完整程式碼請搜尋LittleFall的提交記錄

A-鯤

兩人在周長l的圓環上比賽游泳一週,A的速度始終為a,B的速度為b,兩人同時從起點同向出發,當兩人圓周距離大於k時,B可以選擇反向遊,先回到起點的人勝。給出l,k,a,b,問兩者回到起點的時間差。

A的到達時間恆定為l/a,B只有兩種策略:1.一直向前,2.向前一段再向後。
b>=a則B只能選擇策略1,到達時間為l/b
b<a時,兩者相距為k的時間為k/(ab),策略2總花費時間為2k/(ab)
選擇策略2有一個條件,即B到達終點時A距離終點至少還有

k,即2k/(ab)a<=lk

複雜度O(1)

ll l = read(),k=read(),a=read(),b=read();
double tb = (double)l/b;
if( 2*k<l && b<a && 2*k*a<=(l-k)*(a-b) )
    tb = min(tb, 2.0*k/(a-b));
printf("%.2f\n", tb - (double)l/a );

B-鵬

n個整數的序列,求先上升後下降的段出現了幾次。

判斷上升,再判斷下降,計數。
O(n)

    int n =read(),ans = 0;
    int now = read(),up=0;
    for(int i =1;i<n;i++)
    {
        int t=read();
        if(t>now)
            up=1;
        else if(t<now)
        {
            if(up==1)
            {
                up=0;
                ans++;
            }
        }
        now =t;
    }
    printf
("%d\n",ans );

C-桃花

給出一棵樹,求樹的直徑(最長的鏈)長度。

從根節點dfs一次,找到最遠的點x。再從x開始dfs一次,找到最遠的點y,則x到y是樹的一條直徑。
證明可以百度,大意是反證距離任意點最遠的點都是直徑的端點。
O(n)

vector<int> save[M];
int dis[M];
int ans,xp = -1;

void find(int p)
{
    if(dis[p]>xp)
    {
        xp = dis[p];
        ans = p;
    }
    for(auto x:save[p])
    {
        if(dis[x]==-1)
        {
            dis[x] = dis[p] + 1;
            find(x);
        }
    }
}
int main(void)
{
    int n = read();
    for(int i=1;i<n;i++)
    {
        int a = read(),b=read();
        save[a].push_back(b);
        save[b].push_back(a);
    }
    memset(dis,-1,sizeof(dis));
    dis[1] = 0;
    find(1);
    memset(dis,-1,sizeof(dis));
    xp = -1;
    dis[ans] = 0;
    find(ans);
    printf("%d\n",xp+1 );

    return 0;
}

D-字串絲帶

開一個數組表示遍歷到當前位置時出現過的各個字母數量,再開一個數組記錄每個位置的答案。
O(n+m)

char save[M];
int record[256];
int ans[M];
int main(void)
{
    int n = read(), m=read();
    scanf("%s",save+1);
    for(int i=1;save[i];i++)
    {
        record[save[i]]++;
        ans[i] = record[save[i]];
    }
    for(int i=0;i<m;i++)
    {
        printf("%d\n",ans[read()] );
    }
    return 0;
}

E-對弈

五子棋,給出下棋順序,判斷誰先贏。

每走一步需要判斷輸贏,我的方法是以當前走的棋子為中心判斷左右9格,上下9格,左上右下9格,左下右上9格是否有連續的5個相同顏色棋子。將陣列整體向右下平移即可不考慮邊界。

const int go[4][2] = {{1,1},{0,1},{1,0},{-1,1}};
//1,1 - n,n對應 5,5 - n+4,n+4, 所有棋子橫縱座標+4
int save[M][M];
int main(void)
{
    int n=read(),m=read();
    int ans = 0, ansstep = m, player = -1; //1表示htbest,-1表示whz
    for(int step = 1;step<=m; step++)
    {
        player *= -1;
        int x=read()+4,y=read()+4;
        save[x][y] = player;
        for(int k = 0;k<4;k++)
        {
            int px = x - 4*go[k][0], py = y-4*go[k][1];
            int lx = 0,alx = 0;
            for(int cnt = 0;cnt<9;cnt++)
            {
                if(save[px][py]==player)
                {
                    lx++;
                    alx = max(alx,lx);
                }
                else
                {
                    lx = 0;
                }
                px +=go[k][0],py+=go[k][1];
            }
            if(alx>=5)
            {
                ans = player;
                ansstep = step;
                goto end;
            }
        }
    }
    end:
    if(ans==0)
        printf("UNK %d\n",m);
    else if(ans==1)
        printf("HtBest %d\n",ansstep );
    else
        printf("WHZ %d\n",ansstep );
    return 0;
}

F-發電

1e6個元素,1e6個操作,三種:1.單點乘,2.單點除,3.區間求積,模1e9+7輸出

樹狀陣列改造一下,將加換成乘,0換成1,加上取模,求區間積時用之後的除以之前的。
除以一個數就是乘以它的逆元,用費馬小定理。

複雜度O(nlogn)

class BinTree: vector<ll>
{
public:
    explicit BinTree(int k = 0) //預設初始化一個能儲存k個元素的空樹狀陣列
    {
        assign(k + 1, 1); //有效下標從1開始,0僅作邏輯用處
    }
    int lowbit(int k){return k & -k;}
    ll sum(int k)//求第1個元素到第n個元素的和
    {
        return k > 0 ? (sum(k - lowbit(k)) * at(k))%MOD : 1;
    }
    int last()//返回最後一個元素下標
    {
        return size() - 1;
    }
    void add(int k, ll w) //為節點k加上w
    {
        if(k > last())return;
        at(k) *= w;
        at(k) %= MOD;
        add(k + lowbit(k), w);
    }
};

ll power(ll a, ll b)
{
    ll ans = 1;
    while(b)
    {
        if(b & 1)   ans = ans * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return ans;
}
ll inv(ll a){
    return power(a, MOD - 2);
}


int main(void)
{
    int n=read(),m=read();
    BinTree bt(n);
    while(m--)
    {
        int op=read(),x=read(),y=read();
        if(op==1)
            bt.add(x,y);
        else if(op==2)
            bt.add(x,inv(y));
        else
        {
            ll ans = bt.sum(y) * inv(bt.sum(x-1)) % MOD;
            cout<<ans<<endl;
        }
    }

    return 0;
}

G-指紋鎖

維護一個儲存著值在1e7以內的集合,1e6個操作,包括三種:
1.新增x,若集合內有值與x相差k以內,忽略本次操作;
2.刪除x,刪除集合內所有與x相差k以內的數;
3.查詢x,如果集合內有與x相差k以內的數則輸出Yes,否則輸出No.

用set,預先新增一個特別大的數如1e9+7,如果lower_bound(x-k)<=x+k,說明集合內有值與x相差k以內。

複雜度O(nlogn)

set<int> st;
int select(int val)
{
    auto it = st.lower_bound(val);
    if(it==st.end()) return MOD;
    return *it;
}
int main(void)
{
    int m=read(),k=read();
    char op[10];
    int val;
    for(int i = 0 ;i<m;i++)
    {
        scanf("%s%d",op,&val);
        if(op[0]=='a')
        {
            if(select(val-k) > val+k)
                st.insert(val);
        }
        else if(op[0]=='d')
        {
            int pval = select(val-k);
            while(pval<=val+k)
            {
                st.erase(pval);
                pval = select(val-k);
            }
        }
        else
        {
            printf("%s\n",select(val-k)<=val+k?"Yes":"No" );
        }

    }

    return 0;
}

H-挖溝

最小生成樹。
按最小生成樹做AC了,但是題目中說要求∑d[i][j]最小,我無法證明這個問題可以轉換成最生成樹問題。

Kruskal,O(ElogE)

struct Edge
{
    int x,y,p;
}save[M];
int fa[M];
int getfa(int p)
{
    return p==fa[p]?p:fa[p]=getfa(fa[p]);
}
int main(void)
{
    int n=read(),m=read();
    for(int i=0;i<m;i++)
    {
        save[i].x=read();
        save[i].y=read();
        save[i].p=read();
    }
    sort(save,save+m,[](Edge &a,Edge &b){return a.p<b.p;});
    for(int i =1;i<=n;i++)
        fa[i]=i;
    int ans = 0;
    for(int i=0;i<m;i++)
    {
        int x= getfa(save[i].x),y=getfa(save[i].y);
        if(x!=y)
        {
            ans+=save[i].p;
            fa[y]=x;
        }
    }
    cout <<ans<<endl;

    return 0;
}

I-公交線路

給出一張圖,求s點到t點的最短距離

dijkstra
複雜度O(N^2)

//鏈式前向星
int tot = 0;
int fst[N];
int pnt[M],nxt[M],pri[M];
void add(int x, int y, int prior)
{
    pnt[++tot] = y;
    pri[tot] = prior;
    nxt[tot] = fst[x];
    fst[x] = tot;
}

//dijkstra
int dis[N],vis[N];

int main(void)
{
    #ifdef _LITTLEFALL_
    freopen("in.txt","r",stdin);
    #endif

    int n=read(),m=read(),s=read(),t=read();
    for(int i =0;i<m;i++)
    {
        int x = read(),y=read(),p=read();
        add(x,y,p);
        add(y,x,p);
    }
    //dijkstra
    memset(dis,0x3f,sizeof(dis));
    dis[s] = 0, vis[s]=1;
    int now = s;
    while(now!=t)
    {
        for(int i = fst[now];i;i=nxt[i])
        {
            int y = pnt[i];
            dis[y] = min(dis[y],dis[now]+pri[i]);
        }
        int xm = INT_MAX, tmp = 0;
        for(int i =1;i<=n;i++)
            if(vis[i]==0&&dis[i]<xm)
                tmp = i, xm = dis[i];
        now = tmp;
        vis[now] = 1;
    }
    printf("%d\n",vis[t]?dis[t]:-1 );
    return 0;
}

J-洋灰三角

給定n,k,p,數列a1=1,ai=kai1+p,求an的前n項和Sn
(感謝我的大佬隊友們幫我賽後想清了公式)

如果k=1,那麼an=1+(n1)p,等差數列,Sn=n+pn(n1)/2
否則,an+pk1=k(an1+pk1),等比數列,最後算得

Sn=kn+1+(p1)kn(np+1)k+(n1)p+1(k1)2

同樣需要快速冪和逆元,複雜度O(1)

ll power(ll a, ll b)
{
    ll ans = 1;
    while(b)
    {
        if(b & 1)   ans = ans * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return ans;
}
ll inv(ll a){
    return power(a, MOD - 2);
}
int main(void)
{
    #ifdef _LITTLEFALL_
    freopen("in.txt","r",stdin);
    #endif

    ll n = rea