1. 程式人生 > 實用技巧 >[Nowcoder]牛客網週週練15

[Nowcoder]牛客網週週練15

Before the Beginning

轉載請將本段放在文章開頭顯眼處,如有二次創作請標明。
原文連結:https://www.codein.icu/nowcoderweekly15/

前言

A:亂搞

B:貪心

C:神仙題,貪心+圖論?

D:資料結構

E:數論

A 數列下標

看到範圍,隨便打個 \(O(n^2)\) 水過去,理論上卡滿可能會TLE,但就是過了……

#include <cstdio>
#include <ctype.h>
const int bufSize = 1e6;
inline char nc()
{
    #ifdef DEBUG
    return getchar();
    #endif
    static char buf[bufSize],*p1 = buf,*p2 = buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,bufSize,stdin),p1==p2)?EOF:*p1++;
}
template<typename T>
inline T read(T &r)
{
    static char c;
    r=0;
    for(c = nc();!isdigit(c);) c = nc();
    for(;isdigit(c);c=nc()) r = r * 10 + c - 48;
    return r;
}
const int maxn = 1e4 + 100;
int n;
int a[maxn];
int b[maxn];
int main()
{
    read(n);
    for(int i = 1;i<=n;++i) read(a[i]);
    for(int i = 1;i<=n;++i)
    {
        for(int j = i + 1;j<=n;++j)
            if (a[j] > a[i])
            {
                b[i] = j;
                break;
            }
    }
    for(int i = 1;i<=n;++i) printf("%d ",b[i]);
    return 0;
}

B 可持久化動態圖上樹狀陣列維護01揹包

說實話我都不知道可持久化動態圖是個啥東西……

題目其實是貪心:顯然每次刪除第一個元素,可以讓整體代價最小。

\(a_i < 0\) 時,代價是負數,要求最大,那麼對於 \(a_i\) 而言最大的下標就是原始下標。

#include <cstdio>
#include <algorithm>
#include <ctype.h>
const int bufSize = 1e6;
#define int long long
#define DEBUG
inline char nc()
{
    #ifdef DEBUG
    return getchar();
    #endif
    static char buf[bufSize],*p1 = buf,*p2 = buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,bufSize,stdin),p1==p2)?EOF:*p1++;
}
template<typename T>
inline T read(T &r)
{
    static char c;
    static int flag;
    r=0;
    flag = 1;
    for(c = nc();!isdigit(c);c = nc()) if(c == '-') flag = -1;
    for(;isdigit(c);c=nc()) r = r * 10 + c - 48;
    return r * flag;
}
const int maxn = 1e6 + 100;
int n;
int a[maxn];
signed main()
{
    scanf("%lld",&n);
    for(int i = 1;i<=n;++i) scanf("%lld",a + i);
    long long sum = 0;
    for(int i = 1;i<=n;++i) if(a[i] < 0) sum += i * a[i]; else sum += a[i];
    printf("%lld\n",sum);
    return 0;
}

D 樹上求和

其實這是我打的第二道題……一眼可以用樹剖做,於是就是樹鏈剖分的板子題。

但其實似乎只求子樹用dfs序就可以……?是我蠢了。

線段樹維護平方和也是老套路了:

\(\sum (a_i + k)^2 = \sum a_i^2 + \sum a_i \times k \times 2 + len \times k^2\)

#include <cstdio>
#include <ctype.h>
#define DEBUG
#define int long long
const int bufSize = 1e6;
inline char nc()
{
    #ifdef DEBUG
    return getchar();
    #endif
    static char buf[bufSize],*p1 = buf,*p2 = buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,bufSize,stdin),p1==p2)?EOF:*p1++;
}
template<typename T>
inline T read(T &r)
{
    static char c;
    r=0;
    for(c = nc();!isdigit(c);) c = nc();
    for(;isdigit(c);c=nc()) r = r * 10 + c - 48;
    return r;
}
const int maxn = 1e5 + 100,maxm = 2e5 + 100;
int n,m;
struct node
{
    int to,next;
}E[maxm];
int head[maxn];
inline void add(const int &x,const int &y)
{
    static int tot = 0;
    E[++tot].next = head[x],E[tot].to = y,head[x] = tot;
}
int w[maxn];
int size[maxn],fa[maxn],son[maxn],dfn[maxn],id[maxn],cnt;
long long sum[maxn<<2],tsum[maxn<<2],tag[maxn<<2];
const int mod = 23333;
#define ls p<<1
#define rs p<<1|1
inline void pushup(const int &p){sum[p] = sum[ls] + sum[rs];tsum[p] = tsum[ls] + tsum[rs];sum[p] %= mod,tsum[p] %= mod;}
inline void pushdown(const int &l,const int &r,const int &p)
{
    if(!tag[p]) return;
    int mid = l + r >> 1;
    tsum[ls] += (mid - l + 1) * tag[p] * tag[p] + 2 * sum[ls] * tag[p];
    tsum[rs] += (r - mid) * tag[p] * tag[p] + 2 * sum[rs] * tag[p];
    sum[ls] += (mid - l + 1) * tag[p];
    sum[rs] += (r - mid) * tag[p];
    tag[ls] += tag[p],tag[rs] += tag[p];
    tsum[ls] %= mod,tsum[rs] %= mod,sum[ls] %= mod,sum[rs] %= mod,tag[ls] %= mod,tag[rs] %= mod;
    tag[p] = 0;
}
void build(int l,int r,int p)
{
    if(l == r) return (void)(sum[p] = w[id[l]] % mod,tsum[p] = (w[id[l]] * w[id[l]]) % mod);
    int mid = l + r >> 1;
    build(l, mid, ls), build(mid + 1, r, rs);
    pushup(p);
}
void modify(int l,int r,int p,int ll,int rr,long long k)
{
    if(l >= ll && r <= rr)
    {
        tsum[p] += (r - l + 1) * k * k + 2 * sum[p] * k;
        sum[p] += (r - l + 1) * k;
        tag[p] += k;
        tsum[p] %= mod,sum[p] %= mod,tag[p] %= mod;
        return;
    }
    int mid = l + r >> 1;
    pushdown(l,r,p);
    if(ll <= mid) modify(l,mid,ls,ll,rr,k);
    if(rr > mid) modify(mid + 1,r,rs,ll,rr,k);
    pushup(p);
}
long long ask(int l,int r,int p,int ll,int rr)
{
    if(l >= ll && r <= rr) return tsum[p];
    int mid = l + r >> 1,ans = 0;
    pushdown(l,r,p);
    if(ll <= mid) ans = ask(l,mid,ls,ll,rr) % mod;
    if(rr > mid) ans = (ans + ask(mid + 1,r,rs,ll,rr)) % mod;
    return ans;
}
void dfs1(int u)
{
    size[u] = 1;
    for(int p = head[u];p;p=E[p].next)
    {
        int v = E[p].to;
        if(v == fa[u]) continue;
        fa[v] = u,dfs1(v),size[u] += size[v];
        if(size[son[u]] < size[v]) son[u] = v;
    }
}
void dfs2(int u)
{
    dfn[u] = ++cnt;
    id[cnt] = u;
    if(!son[u]) return;
    dfs2(son[u]);
    for(int p = head[u];p;p=E[p].next)
    {
        int v = E[p].to;
        if(v == son[u] || v == fa[u]) continue;
        dfs2(v);
    }
}
signed main()
{
    read(n),read(m);
    for(int i = 1;i<=n;++i) read(w[i]);
    for(int i = 1;i<n;++i)
    {
        int a,b;
        read(a),read(b);
        add(a,b),add(b,a);
    }
    dfs1(1),dfs2(1);
    build(1,n,1);
    while(m--)
    {
        int opt,x,y;
        read(opt),read(x);
        if(opt == 1) read(y),modify(1,n,1,dfn[x],dfn[x] + size[x] - 1,y);
        else printf("%lld\n",ask(1,n,1,dfn[x],dfn[x] + size[x] - 1) % mod);
    }
    return 0;
}

E 算式子

數論題。

顯然左半邊右半邊可以分開計算。

不知道為啥用整除分塊會TLE自閉到懷疑人生

定義:

\(\operatorname{cnt}(i)\) 為值 \(\leq i\) 的元素的數量。

\(\operatorname{ans}(i)\)\(x = i\) 時,\(\sum \lfloor \dfrac{a_i}{x} \rfloor\) 的值。

我們可以列舉 \(x\)

對於一個 \(x\),可以列舉右半邊的商 \(k\),即:

\(\lfloor \dfrac{x}{a_i} \rfloor = k\)

那麼符合條件的 \(a_i\) 顯然在一個連續的值域範圍內,即:

\(l = k \times x, r = (k + 1) \times x - 1\)

對於這個範圍內的 \(a_i\),對答案的貢獻都為 \(k\),這一段內的貢獻就是 \(k \times num\)

可以使用 \(cnt\) 陣列計算 \(num\)

此時計算完右半邊式子的答案了,考慮如何計算左半邊式子。

左半邊式子乍一看不好處理,但可以發現對於每個 \(a_i\),有一段連續的 \(x\) 的答案是相同的,啟示我們用類似的方法。

列舉 \(a_i\) 的值域 \(i\) 與商 \(k\),同理可以得到:

\(l = k \times i, r = (k + 1) \times i - 1\)

那麼對於 \([l,r]\) 中的 \(x\),這個 \(i\) 值對左半邊的貢獻為 \(num \times k\),即 \(i\) 值出現的次數乘上商。

對於這個區間修改,可以用差分處理,在 \(l\) 點加上貢獻,在 \(r + 1\) 點減去貢獻,最後做字首和即可。

那麼就可以預處理出 \(ans\) 陣列了。

#include <cstdio>
#include <algorithm>
#include <ctype.h>
const int bufSize = 1e6;
#define DEBUG
#define int long long
inline char nc()
{
    #ifdef DEBUG
    return getchar();
    #endif
    static char buf[bufSize],*p1 = buf,*p2 = buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,bufSize,stdin),p1==p2)?EOF:*p1++;
}
template<typename T>
inline T read(T &r)
{
    static char c;
    r=0;
    for(c = nc();!isdigit(c);) c = nc();
    for(;isdigit(c);c=nc()) r = r * 10 + c - 48;
    return r;
}
const int maxn = 2e6 + 100;
int n,m;
int a[maxn];
int cnt[maxn<<1],ans[maxn<<1];
signed main()
{
    read(n),read(m);
    for(int i = 1;i<=n;++i) read(a[i]),cnt[a[i]]++;

    for(int i = 1;i <= m; i++) if(cnt[i])
        for (int k = 1; i * k <= m; ++k)
        {
            int l = k * i, r = (k + 1) * i - 1;
            ans[l] += cnt[i] * k, ans[r + 1] -= cnt[i] * k;
        }
    for (int i = 1; i <= 2 * m; ++i) cnt[i] += cnt[i - 1];
    for (int i = 1; i <= 2 * m; ++i) ans[i] += ans[i - 1];
    long long last = 0;
    for(int i = 1;i<=m;++i)
    {
        long long sum = 0;
        /* 
        for (int l = 1, r, k; l <= i; l = r + 1)
        {
            k = i / l, r = i / k;
            sum += k * (cnt[r] - cnt[l - 1]);
        }
        */
        sum += ans[i];
        for (int k = 1; i * k <= m; ++k)
        {
            int l = k * i, r = (k + 1) * i - 1;
            sum += k * (cnt[r] - cnt[l - 1]);
        }
        
        last ^= sum;
    }
    printf("%lld\n",last);
    return 0;
}

C 璀璨光滑

首先推幾個結論:

  1. 原編號為 \(1\) 的點新編號為 \(0\) ,可使字典序最小。
  2. 距離新編號為 \(0\) 的點,最短距離為 \(x\) 的點,新編號中有 \(x\)\(1\)。考慮從 \(0\) 號點開始走,每次增加一個 \(1\),最少 \(x\) 步後才能走出 \(x\)\(1\),且該距離為最小距離,若不如此走,分兩種情況:將新編號中 \(1\) 位走成 \(0\) 或將新編號中 \(0\) 位走成 \(1\),則都需要走回頭路,因此距離更長。
  3. 新編號含有 \(x\)\(1\) 的點,僅與若干個含有 \(x + 1\)\(1\) 的點與含有 \(x - 1\)\(1\) 的點聯通,且若干個含有 \(x - 1\)\(1\) 的點新編號的或和即為該點新編號。考慮含有 \(x - 1\)\(1\) 的點,根據第二條結論發現它們離 \(0\) 的距離為 \(x - 1\),則其直接與含有 \(x\)\(1\) 的點聯通的路徑即為其最短路。因此可以推出,這些點與含有 \(x\)\(1\) 的點僅相差一個包含在 \(x\)\(1\) 中的 \(1\),且每個點相差的 \(1\) 位置都不同。這些點或起來,即可得到含有 \(x\)\(1\) 的點的新編號。
  4. 交換二進位制位列,對圖性質無影響。例如有點:101 001 010,將第一列交換至第三列,第二列交換至第一列,第三列交換至第二列,新編號為:011 010 100,而原先的圖關係不變。
  5. 將所有點的二進位制編號看做矩陣,行為點原編號,列為二進位制位,值為對應的 \(0\)\(1\),那麼交換任意兩列,圖的性質不變。由於字典序,應當讓原編號小的對應的 \(1\) 的列排在低位。

可以看到,推出的前三個結論與距離密切相關。

整個圖可以看做一圈一圈的、距離依次加一的幾層,而用該層編號即可推出下層編號,因此可以對原圖做一遍寬度優先搜尋。

將與原 \(1\) 號點相連的點隨意分配初始編號,進行一次寬度優先搜尋,即可找出一組可行解。

再根據後兩個結論,將列進行排序,即可找出最優解。

#include <cstdio>
#include <algorithm>
#include <ctype.h>
const int bufSize = 1e6;
inline char nc()
{
#ifdef DEBUG
    return getchar();
#endif
    static char buf[bufSize], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++;
}
template <typename T>
inline T read(T &r)
{
    static char c;
    r = 0;
    for (c = nc(); !isdigit(c);)
        c = nc();
    for (; isdigit(c); c = nc())
        r = r * 10 + c - 48;
    return r;
}
const int maxn = 2e6 + 100, maxm = 5e6 + 100;
int T, n, m;
struct node
{
    int to, next;
} E[maxm];
int head[maxn], tot;
inline void add(const int &x, const int &y)
{
    E[++tot].next = head[x], E[tot].to = y, head[x] = tot;
}
int q[maxn], qt, qh;
int f[maxn], vis[maxn], inq[maxn];
struct A
{
    bool s[maxn];
    int id;
} P[22];
bool cmp(const A &a, const A &b)
{
    for (int i = 1; i <= (1 << n); ++i) 
        if (a.s[i] != b.s[i]) return a.s[i] > b.s[i];
    return 1;
}
int main()
{
    read(T);
    while (T--)
    {
        read(n), read(m);
        qh = 1, qt = 0;
        tot = 0;
        for (int i = 1; i <= (1 << n); ++i)
            vis[i] = f[i] = inq[i] = head[i] = 0;
        for (int i = 1; i <= m; ++i)
        {
            int a, b;
            read(a), read(b);
            add(a, b), add(b, a);
        }
        vis[1] = 1;
        for (int p = head[1], t = 0; p; p = E[p].next)
        {
            int v = E[p].to;
            f[v] = 1 << (t++);
            q[++qt] = v;
        }
        while (qt >= qh)
        {
            int u = q[qh++];
            vis[u] = 1,inq[u] = 0;
            for (int p = head[u]; p; p = E[p].next)
            {
                int v = E[p].to;
                if (vis[v]) continue;
                f[v] |= f[u];
                if (!inq[v]) q[++qt] = v, inq[v] = 1;
            }
        }
        for (int i = 0; i < n; ++i) P[i].id = i;
        for (int i = 1; i <= (1 << n); ++i) 
            for (int j = 0; j < n; ++j)
                P[j].s[i] = (f[i] >> j) & 1;
        std::sort(P, P + n, cmp);
        for (int i = 1; i <= (1 << n); ++i)
        {
            int res = 0;
            for (int j = 0; j < n; ++j) if (P[j].s[i]) res |= (1 << j);
            printf("%d ", res);
        }
        putchar('\n');
    }
    return 0;
}