2019 ICPC 上海區域賽總結


2019上海區域賽現場賽總結

補題情況(以下通過率為牛客提交):

題號 標題 已通過代碼 通過率 我的狀態
A Mr. Panda and Dominoes 點擊查看 5/29 未通過
B Prefix Code 點擊查看 249/1019 通過
C Maze 點擊查看 8/110 未通過
D Spanning Tree Removal 點擊查看 88/211 通過
E Cave Escape 點擊查看 53/553 通過
F A Simple Problem On A Tree 點擊查看 66/290 通過
G Play the game SET 點擊查看 2/35 未通過
H Tree Partition 點擊查看 73/175 通過
I Portal 點擊查看 3/3 未通過
J Bob's Poor Math 點擊查看 15/105 通過
K Color Graph 點擊查看 154/344 通過
L Light It Down 點擊查看 1/3 未通過
M Blood Pressure Game 點擊查看 3/81 未通過

B

可以用字典樹,但實際unordered_map就可以了,更好寫一點

代碼連接

D

這個題比較搞,但其實想出來的話特別好寫(現場賽做崩了,298 1A)

題意是給一個 n (\(n\le 1000\)) 個點的完全圖 , 每次刪除一個n結點的生成樹(在原圖刪除對應的邊),問最多可以刪除多少次。

容易想到 n 個點的完全圖總邊數是 \(n*(n-1) / 2\) ,而一個生成樹的邊數為 \((n-1)\) ,所以最多可以構造出\(n/2\) 棵生成樹。

n 為偶數時,枚舉\((x, y),x=2k-1,y=2k,k\in[1,n/2]\) , 將 \(x\) 與小於\(x\) 的偶數相連,與大於\(x\) 的奇數相連、將\(y\) 與小於 \(y\) 的奇數相連,與大於\(y\) 的偶數相連。

如此構造可以使得:

  1. 每個偶數都與比它大的偶數和比它小的奇數相連,都被比它小的偶數和比它大的奇數相連
  2. 每個奇數都與比它小的偶數和比它大的奇數相連,都被比它大的偶數和比它小的奇數相連

可以發現,每個點都只與其他\((n-1)\) 個點搭配了一次。

對於 n 為奇數的情況,先按照\((n-1)\) 為偶數的情況去處理,然后在第 \(i\)\((n-1)\)結點的生成樹中,將\((i,n)\)相連即可。

int T, n, cas;
void get(int s, int t, int n){
    for (int i = 2; i < s;i += 2)
        printf("%d %d\n", i, s);
    for (int i = s + 2; i <= n;i += 2)
        printf("%d %d\n", s, i);
    for (int i = 1; i < t; i += 2)
        printf("%d %d\n", i, t);
    for (int i = t + 2; i <= n;i+=2)
        printf("%d %d\n", t, i);
}
int main() {
    scanf("%d", &T);
    while(T--){
        scanf("%d", &n);
        printf("Case #%d: %d\n", ++cas, n / 2);
        for (int i = 1; i < n;i+=2){
            if(n & 1)
                printf("%d %d\n", i, n);
            get(i, i + 1, n - (n & 1));
        }
    }
    return 0;
}

關於怎么想到這么一種構造方法...首先只考慮n為偶數的情況,然后每次枚舉兩個點,做為整個樹的“中心樞紐", 其他點各分一半與這兩個點相連,按照規律去找。

E

比較裸的最大生成樹,如果不是多組數據就可以裸着做了。

T = 10,點數1e6, 邊數4e6級別。對邊排序或者堆優化的prim都不太靠得住,但是注意下題目數據就會發現,邊權最大只可能是10000,所以按照桶排的思想去處理邊即可。然后kruskal。

const int N = 1000 + 5;
typedef pair<int, int> pii;
int T,n,m;
pii st, ed;
int val[N][N];
int x[1000010],A, B, C, P;
int id[N][N], vis[N][N], fa[N*N];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
vector<pii> edge[10010];
int main()
{
    scanf("%d", &T);
    int cas = 0;
    while(T--){
        scanf("%d%d%d%d%d%d", &n, &m, &st.first, &st.second, &ed.first, &ed.second);
        scanf("%d%d%d%d%d%d", &x[1], &x[2], &A, &B, &C, &P);
        for (int i = 1; i <= n*m;i++)
            fa[i] = i;
        for (int i = 0; i <= 10000;i++)
            edge[i].clear();
        for (int i = 3; i <= n * m; i++)
        {
            x[i] = (A * x[i - 1] + B * x[i - 2] + C) % P;
        }
        int k = 0;
        for (int i = 1; i <= n;i++)
            for (int j = 1; j <= m;j++){
                id[i][j] = ++k;
                val[i][j] = x[k];
                if(i > 1)
                    edge[val[i][j] * val[i - 1][j]].push_back({ id[i][j], id[i - 1][j] });
                if(j > 1)
                    edge[val[i][j] * val[i][j - 1]].push_back({ id[i][j], id[i][j - 1] });
            }
        ll res = 0, cnt = 0;
        for (int i = 10000; i >= 0;i--){
            for (int j = 0; j < edge[i].size();j++){
                int x = edge[i][j].first, y = edge[i][j].second;
                if(find(x) == find(y))
                    continue;
                res += i;
                fa[find(x)] = find(y);
                cnt++;
                if(cnt == k-1)
                    break;
            }
            if(cnt == k-1)
                break;
        }
            printf("Case #%d: %lld\n", ++cas, res);
    }
    return 0;
}

F

顯然是樹剖,不過要維護的東西有那么一點惡心人。

維護\(\sum w^3\) ,從三個操作去分解它,得到線段樹中需要存放的東西

  1. 區間賦值為 w
  2. 區間加 w
  3. 區間乘 w

為了簡化線段樹的操作,發現1號操作等價於先乘一個0,在加一個 w。所以這三個操作都可以轉換為這個形式:區間先乘一個數 x,再加一個數 y

要維護 \(\sum w^3\) ,乘的操作看起來比較容易,所以先來看看加一個數如何維護

\((w+y)^3 = w^3 + y^3 + 3*w^2*y + 3*w*y^2\)

\(\Rightarrow \sum (x+y)^3 = \sum w^3 + \sum y^3 + 3*\sum w^2*y + 3*\sum w* y^2\)

發現線段樹中需要維護三個值:\(\sum w^3, \sum w^2, \sum w\)

這三個值分別用 \(s3, s2, s\) 表示

對於乘法更新:

\[s3 = s3 * x^3\\ s2 = s2 * x ^2\\ s = s * x \]

對於加法更新(注意變量更新順序):

\[s3 = s3 + \sum y^3 + 3*s2*y + 3*s*y^2\\ s2 = s2 + 2*s*y+\sum y*y\\ s = s + \sum y \]

lazy_tag 更新:

\[mul = mul *x \\ add = add * x + y \]

代碼

由於絕大部分是板子,所以代碼列出主要部分, 下面是線段樹的操作代碼:

#define ls (p*2)
#define rs (p*2+1)
struct SegTree{
    int l, r;
    ll len, s, s2, s3, mul, add;
} t[N << 2];
void pushup(int p){
    t[p].s = (t[ls].s + t[rs].s) % mod;
    t[p].s2 = (t[ls].s2 + t[rs].s2) % mod;
    t[p].s3 = (t[ls].s3 + t[rs].s3) % mod;
}
void build(int p,int l,int r){
    t[p].l = l, t[p].r = r;
    t[p].mul = 1;
    t[p].add = t[p].s = t[p].s2 = t[p].s3 = 0;
    t[p].len = r - l + 1;
    if(l == r){
        ll val = w[rnk[l]];
        t[p].s = val;
        t[p].s2 = val * val % mod;
        t[p].s3 = val * val % mod * val % mod;
        return;
    }
    int mid = l + r >> 1;
    build(p * 2, l, mid);
    build(p * 2 + 1, mid + 1, r);
    pushup(p);
}
ll S3(ll x) { return x * x % mod * x % mod; }
ll S2(ll x) { return x * x % mod; }
void calc(int p,ll x, ll y){ // 這里千萬要細心
    //乘法更新
    t[p].s = t[p].s * x % mod;
    t[p].s2 = t[p].s2 * S2(x) % mod;
    t[p].s3 = t[p].s3 * S3(x) % mod;
    //加法更新,這里要按照s3,s2,s的順序依次更新
    t[p].s3 = t[p].s3 + S3(y) * t[p].len % mod + 3ll * t[p].s2 * y % mod + 3ll * t[p].s * S2(y) % mod;
    t[p].s3 %= mod;
    t[p].s2 = (t[p].s2 + 2ll * t[p].s * y % mod + S2(y)*t[p].len) % mod;
    t[p].s = (t[p].s + y * t[p].len % mod) % mod;
    //最后也不能掉以輕心
    t[p].mul = t[p].mul * x % mod;
    t[p].add = (t[p].add * x % mod + y) % mod;
}
void pushdown(int p){
    if(t[p].mul != 1 || t[p].add){
        calc(ls, t[p].mul, t[p].add);
        calc(rs, t[p].mul, t[p].add);
        t[p].mul = 1, t[p].add = 0;
    }
}
void change(int p,int l,int r,ll x,ll y){
    if(t[p].l >= l && t[p].r <= r){
        calc(p, x, y);
        return;
    }
    pushdown(p);
    int mid = t[p].l + t[p].r >> 1;
    if(mid >= l)
        change(ls, l, r, x, y);
    if(mid < r)
        change(rs, l, r, x, y);
    pushup(p);
}
ll query(int p,int l,int r){
    if(t[p].l >= l && t[p].r <= r)
        return t[p].s3;
    pushdown(p);
    int mid = t[p].l + t[p].r >> 1;
    ll res = 0;
    if(mid >= l)
        res += query(ls, l, r);
    if(mid < r)
        res += query(rs, l, r);
    return res % mod;
}

H

二分+樹形DP

枚舉最小值mid,按照這個值對樹進行分塊。

對於 \(x\) 結點,遞歸處理它的子節點,並將所有子節點的剩余價值放到一個容器中,進行排序,優先考慮小的。不能裝的時候,后面的要全部割掉。

typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100000 + 5;
int n, k, cnt, head[N], ver[N<<1], nxt[N<<1], tot;
ll w[N], d[N], lim;
void add(int x,int y){
    ver[++tot] = y, nxt[tot] = head[x], head[x] = tot;
}
void dfs(int x,int fa){
    vector<ll> val;//放在內部還是外部需要仔細考慮,如果貪戀時間,可以放外面,不過要注意要先dfs所有子節點后,再掃一遍將他們加入到序列中,函數返回時要記得clear
    for (int i = head[x]; i;i=nxt[i]){
        int y = ver[i];
        if(y == fa)
            continue;
        dfs(y, x);
        val.push_back(d[y]);
    }
    sort(val.begin(), val.end());
    d[x] = w[x];
    for (int i = 0; i < val.size();i++){
        if(d[x] + val[i] > lim){
            cnt += val.size() - i;
            break;
        }
        d[x] += val[i];
    }
}
bool check(ll mid){
    lim = mid;
    cnt = 1;
    dfs(1, 0);
    return cnt <= k;
}
int main() {
    int T, cas = 0;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &k);
        tot = 0;
        for (int i = 1; i <= n;i++)
            head[i] = 0;
        for (int i = 1; i < n; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            add(x, y);
            add(y, x);
        }
        ll l = 1, r = 0;
        for (int i = 1; i <= n;i++){
            scanf("%lld", &w[i]);
            r += w[i];
            l = max(l, w[i]);
        }
        while(l < r){
            ll mid = l + r >> 1;
            if(check(mid))
                r = mid;
            else
                l = mid + 1;
        }
        printf("Case #%d: %lld\n", ++cas, l);
    }
    return 0;
}

J

字典樹,類似線段樹的操作,細節挺多

  1. Add 操作,直接在字典樹中加一個新的數字
  2. Shift操作,new一個新的根,將原根接到新根的0號位置,並更新sum值,打tag標記
  3. Query操作,每一層通過枚舉找到一個最大可以組合成的數字
  4. Sum 操作,類似線段樹的區間求和
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 200000 + 5;
int tr[N][10], n, m, rt, tot;
ll sum[N], tag[N], l, r, x, p[11];
char op[10];
void clear(){
    for (int i = 1; i <= tot;i++){
        memset(tr[i], 0, sizeof tr[i]);
        sum[i] = tag[i] = 0;
    }
    tot = rt = 1;
}
ll add_val(ll x, ll y){//按照題意的加法原則進行相加
    if(x == 0 || y == 0)
        return x | y;
    ll res = 0;
    for (int i = 0; i < 10;i++, x /= 10, y /= 10){
        res += (x % 10 + y % 10) % 10 * p[i];
    }
    return res;
}
void pushdown(int u){
    if(tag[u] == 0)
        return;
    for(int i = 0; i <= 9;i++){
        if(!tr[u][i])
            continue;
        int v = tr[u][i];
        if(tag[u] > 10)
            sum[v] = 0;
        else
            sum[v] /= p[tag[u]], tag[v] += tag[u];
    }
    tag[u] = 0;
}
void add(int u,int x){
    sum[u] = add_val(sum[u], x);
    for (int i = 9; i >= 0;i--){
        pushdown(u);
        int c = x / p[i] % 10;
        if(tr[u][c] == 0)
            tr[u][c] = ++tot;
        u = tr[u][c];
        sum[u] = add_val(sum[u], x);
    }
}
void shift(){
    tr[++tot][0] = rt;
    sum[tot] = sum[rt] / 10;
    tag[rt = tot]++;
}
ll query_sum(ll x){
    ll res = 0;
    int u = rt;
    for (int i = 9; i >= 0;i--){
        pushdown(u);
        int c = x / p[i] % 10, j = 9;
        //找到最大的可組合的 j
        while(tr[u][(j+10-c)%10] == 0)
            j--;
        res += j * p[i];
        u = tr[u][(j + 10 - c) % 10];
    }
    return res;
}
ll get(int dep, int u, ll l, ll r, ll x, ll y){
    if(y < l || x > r)
        return 0;
    if(l >= x && r <= y){
        return sum[u];
    }
    pushdown(u);
    ll res = 0;
    for (int i = 0; i <= 9;i++){
        if(!tr[u][i])
            continue;
        //注意這里的[nl,nr]的計算
        ll nl = l + p[dep] * i, nr = l + p[dep] * (i + 1) - 1;
        res = add_val(res, get(dep - 1, tr[u][i], nl, nr, x, y));
    }
    return res;
}
int main() {
    p[0] = 1;
    for (int i = 1; i <= 10;i++)
        p[i] = p[i - 1] * 10;
    int T, cas = 0;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &m);
        clear();
        for (int i = 1; i <= n;i++){
            scanf("%lld", &x);
            add(rt, x);
        }
        printf("Case #%d:\n", ++cas);
        while(m--){
            scanf("%s", op);
            if(op[0] == 'S' && op[1] == 'h')
                shift();
            else if(op[0] == 'A'){
                scanf("%lld", &x);
                add(rt, x);
            }else if(op[0] == 'Q'){
                scanf("%lld", &x);
                printf("%lld\n", query_sum(x));
            }else if(op[0] == 'S'){
                scanf("%lld%lld", &l, &r);
                printf("%lld\n", get(9, rt, 0, p[10] - 1, l, r));
            }
        }
    }
    return 0;
}

K

染邊不存在奇環,那么染邊與點構成一個二分圖,枚舉每個點在這個二分圖中的所屬,將所有邊進行枚舉,如果該邊的兩個點在二分圖中所屬不同,那么貢獻++

const int N = 16 + 5;
int T, n, m, st[N];
int cas;
pair<int, int> e[N * N];
int main() {
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m;i++){
            scanf("%d%d", &e[i].first, &e[i].second);
        }
        int res = 0;
        for (int i = 0; i < (1 << n);i++){
            for (int j = 0; j < n;j++){
                if(i >> j & 1)
                    st[j + 1] = 1;
                else
                    st[j + 1] = 0;
            }
            int cur = 0;
            for (int i = 1; i <= m;i++){
                if(st[e[i].first] != st[e[i].second]){
                    cur++;
                }
            }
            res = max(res, cur);
        }
        printf("Case #%d: %d\n", ++cas, res);
    }
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM