Codeforces Round #709 (Div. 2, based on Technocup 2021 Final Round)

传送门

Codeforces Round #709 (Div. 2, based on Technocup 2021 Final Round)

A

每个格子至少取一条边进行删除,则删除的格子数 n ≥ a × b n\geq a\times b na×b;若能构造出一个解使 n = a × b n=a\times b n=a×b,则可证明答案为 a × b a\times b a×b。对于每一行,取格子左侧的边进行删除,即可使所有格子都存在一条通向监狱外侧的路径,此时删除的边数为 a × b a\times b a×b

#include <bits/stdc++.h>
using namespace std;
int T;
 
int main()
{
    
    
    scanf("%d", &T);
    while (T--)
    {
    
    
        int a, b;
        cin >> a >> b;
        cout << a * b << endl;
    }
    return 0;
}
B

首先预处理 n = 1 n=1 n=1 的情况。当 n > 1 n>1 n>1,即可临项做差分,得到差分数组 d d d。由于 0 ≤ c < m 0\leq c<m 0c<m,且每一项都保证为 m m m 的模数,则 d d d 的可能取值至多存在 2 2 2 种可能,即 c c c c − m c-m cm

那么用一个集合维护 d d d 中的数字,若集合的大小大于 2 2 2,显然无解。若集合大小为 2 2 2,则需要保证集合中的元素为一正一负,此时 m m m 存在唯一解 c − ( c − m ) = m c-(c-m)=m c(cm)=m,最后还要判断 a a a 中的数字是否都满足对 m m m 取模,即 a i < m a_i<m ai<m。若集合大小为 1 1 1,设这个元素为 d d d,若 d ≥ 0 d\geq 0 d0,则 m m m 可以取任意大的值;若 d < 0 d<0 d<0,此时有 d = c − m d=c-m d=cm,此时并不存在对 c c c 的约束条件,则 c c c 可以取任意大的值, m m m 依然可以取任意大的值。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
int T, N, A[maxn];

int main()
{
    
    
    scanf("%d", &T);
    while (T--)
    {
    
    
        scanf("%d", &N);
        int mx = 0;
        for (int i = 1; i <= N; ++i)
            scanf("%d", A + i), mx = max(mx, A[i]);
        if (N == 1)
        {
    
    
            puts("0");
            continue;
        }
        unordered_set<int> st;
        for (int i = 2; i <= N; ++i)
            st.insert(A[i] - A[i - 1]);
        int res = -1, c = -1;
        if (st.size() == 2)
        {
    
    
            int a = *st.begin(), b = *++st.begin();
            if ((a > 0 && b < 0) || (a < 0 && b > 0))
            {
    
    
                if (a < b)
                    swap(a, b);
                res = a - b, c = a;
            }
        }
        else if (st.size() == 1)
            res = 0;
        if (res > 0 && res <= mx)
            res = -1;
        if (res == 0 || res == -1)
            printf("%d\n", res);
        else
            printf("%d %d\n", res, c);
    }
    return 0;
}
C

c n t i cnt_i cnti 代表友人 i i i 有空的天数。若 ∀ i ∈ n , c n t i ≤ ⌈ m / 2 ⌉ \forall i\in n,cnt_i\leq \lceil m/2\rceil in,cntim/2,则可以任意构造一组可行解。反之,一定存在一个 x x x,满足 c n t x > ⌈ m / 2 ⌉ cnt_x> \lceil m/2\rceil cntx>m/2,求出其中一个友人 x x x,考虑分配 x x x 组队 ⌈ m / 2 ⌉ \lceil m/2\rceil m/2 次,此时无论其他人怎么分配,都满足条件。若某一天 j j j 只有 x x x 有空,则分配 x x x 在第 j j j 天组队,若这样的天数大于 ⌈ m / 2 ⌉ \lceil m/2\rceil m/2,显然无解;反之,若 x x x 分配的必要天数 n u m num num 小于 ⌈ m / 2 ⌉ \lceil m/2\rceil m/2,则任意分配其余 ⌈ m / 2 ⌉ − n u m \lceil m/2\rceil-num m/2num 天的任务给 x x x

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
int T, N, M, cnt[maxn], res[maxn];
vector<int> K[maxn];

int main()
{
    
    
    scanf("%d", &T);
    while (T--)
    {
    
    
        scanf("%d%d", &N, &M);
        memset(res, 0, sizeof(res));
        memset(cnt, 0, sizeof(cnt));
        for (int i = 1; i <= M; ++i)
            K[i].clear();
        for (int i = 1, k, f; i <= M; ++i)
        {
    
    
            scanf("%d", &k);
            for (int j = 1; j <= k; ++j)
                scanf("%d", &f), ++cnt[f], K[i].push_back(f);
        }
        int x = 0, lim = ceil((double)M / 2);
        for (int i = 1; i <= N; ++i)
            if (cnt[i] > lim)
            {
    
    
                x = i;
                break;
            }
        if (x == 0)
        {
    
    
            puts("YES");
            for (int i = 1; i <= M; ++i)
                printf("%d%c", K[i][0], i == M ? '\n' : ' ');
            continue;
        }
        int num = lim;
        for (int i = 1; i <= M; ++i)
            if (K[i].size() == 1 && K[i][0] == x)
                res[i] = x, --num;
        if (num < 0)
        {
    
    
            puts("NO");
            continue;
        }
        for (int i = 1; i <= M; ++i)
            if (!res[i] && num > 0)
                for (auto y : K[i])
                    if (y == x)
                    {
    
    
                        res[i] = x, --num;
                        break;
                    }
        for (int i = 1; i <= M; ++i)
            if (!res[i])
                for (auto y : K[i])
                    if (y != x)
                    {
    
    
                        res[i] = y;
                        break;
                    }
        puts("YES");
        for (int i = 1; i <= M; ++i)
            printf("%d%c", res[i], i == M ? '\n' : ' ');
    }
    return 0;
}
D

暴力模拟 O ( N 2 ) O(N^2) O(N2),考虑优化模拟的过程。首先,需要高效的维护任一元素的下一个满足 g c d ( a i , a n x t [ i ] ) = 1 gcd(a_i,a_{nxt[i]})=1 gcd(ai,anxt[i])=1 的位置 i i i;其次,需要高效地维护任一元素的下一未删除元素。

可以使用 2 2 2 个并查集维护上述关系。设两类并查集父节点数组分别为 f 1 , f 2 f_1,f_2 f1,f2。对于第一类并查集,若 g c d ( i , n x t [ i ] > 1 ) gcd(i,nxt[i]>1) gcd(i,nxt[i]>1),则 f 1 [ i ] = n x t [ i ] f_1[i]=nxt[i] f1[i]=nxt[i],此时对于每一个节点 i i i,其根节点即节点 i i i 的下一个满足 g c d ( a j , a n x t [ j ] ) = 1 gcd(a_j,a_{nxt[j]})=1 gcd(aj,anxt[j])=1 的位置 j j j;根据删除原则,此时删除的 n x t [ j ] nxt[j] nxt[j] 是并查集某棵树上的叶子节点,删除后不会对其他接节点产生影响。对于第二类关系,当删除节点 i i i 的元素,则 f 2 [ i ] = i m o d    n + 1 f_2[i]=i\mod n+1 f2[i]=imodn+1,此时对于每一个节点 i i i,其根节点即节点 i i i 的下一未删除元素所在的节点。

维护 2 2 2 个变量 n 1 , n 2 n_1,n_2 n1,n2,分别代表 2 2 2 个并查集中树的数量。若 n 1 = 0 n_1=0 n1=0,则当前序列都满足相邻元素 g c d ( a j , a n x t [ j ] ) > 1 gcd(a_j,a_{nxt[j]})>1 gcd(aj,anxt[j])>1,无可删除元素;若 n 2 = 0 n_2=0 n2=0,则当前序列元素个数为 0 0 0,过程无法继续进行。当出现上述情况之一,停止模拟即可。需要注意的是, f 2 f_2 f2 节点合并时对应了 f 1 f_1 f1 中节点的删除,若当前删除节点为 f 1 f_1 f1 中的根节点,则 n 1 n_1 n1 减一。总时间复杂度 O ( N ( log ⁡ A + log ⁡ N ) ) O(N(\log A+\log N)) O(N(logA+logN))

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
int T, N, A[maxn], f1[maxn], f2[maxn];
vector<int> res;

int find(int x, int *fa) {
    
     return fa[x] == x ? x : (fa[x] = find(fa[x], fa)); }

int gcd(int a, int b) {
    
     return b ? gcd(b, a % b) : a; }

int main()
{
    
    
    scanf("%d", &T);
    while (T--)
    {
    
    
        res.clear();
        scanf("%d", &N);
        for (int i = 1; i <= N; ++i)
            scanf("%d", A + i);
        int n1 = N, n2 = N;
        for (int i = 1; i <= N; ++i)
            f1[i] = f2[i] = i;
        for (int i = 1; i < N; ++i)
            if (gcd(A[i], A[i + 1]) > 1)
                f1[i] = i + 1, --n1;
        if (gcd(A[1], A[N]) > 1)
            f1[N] = 1, --n1;
        int x = 1, y;
        while (n1 && n2)
        {
    
    
            x = find(x, f1), y = find(x % N + 1, f2);
            res.push_back(y);
            f2[y] = y % N + 1, --n2;
            if (!n2)
                break;
            if (f1[y] == y)
                --n1;
            y = find(y, f2);
            if (x == y)
            {
    
    
                if (A[x] == 1)
                    res.push_back(x);
                break;
            }
            if (gcd(A[x], A[y]) > 1)
                f1[x] = y, --n1;
            x = y;
        }
        printf("%d", (int)res.size());
        for (auto x : res)
            printf(" %d", x);
        putchar('\n');
    }
    return 0;
}

每次操作只关注当前删除节点的下一未删除节点,且操作顺序执行,则可更高效地维护上述关系。

使用链表维护当前节点的下一未删除节点;使用队列维护满足 g c d ( A [ i ] , A [ n x t [ i ] ] ) = 1 gcd(A[i],A[nxt[i]])=1 gcd(A[i],A[nxt[i]])=1 的相邻元素。队列中的每一对元素 ( i , j ) (i,j) (i,j),只有前一个 i i i 可能被删除;每次取出队首,判断元素对存在后,将后一元素 j j j 删除,将队首元素指向 n x t [ j ] nxt[j] nxt[j],并判断 ( i , n x t [ i ] ) (i,nxt[i]) (i,nxt[i]) 是否为满足 g c d ( A [ i ] , A [ n x t [ i ] ] ) = 1 gcd(A[i],A[nxt[i]])=1 gcd(A[i],A[nxt[i]])=1 的数对,若是,则插入队尾。总时间复杂度 O ( N log ⁡ A ) O(N\log A) O(NlogA)

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> P;
const int maxn = 100005;
int T, N, A[maxn], nxt[maxn];
bool del[maxn];
vector<int> res;
queue<P> Q;

int gcd(int a, int b) {
    
     return b ? gcd(b, a % b) : a; }

int main()
{
    
    
    scanf("%d", &T);
    while (T--)
    {
    
    
        memset(del, 0, sizeof(del));
        res.clear();
        scanf("%d", &N);
        for (int i = 1; i <= N; ++i)
            scanf("%d", A + i);
        for (int i = 1; i < N; ++i)
            nxt[i] = i + 1;
        nxt[N] = 1;
        for (int i = 1; i < N; ++i)
            if (gcd(A[i], A[i + 1]) == 1)
                Q.push(P(i, i + 1));
        if (gcd(A[N], A[1]) == 1)
            Q.push(P(N, 1));
        while (Q.size())
        {
    
    
            int j = Q.front().first, k = Q.front().second;
            Q.pop();
            if (del[j])
                continue;            
            res.push_back(k);
            nxt[j] = nxt[k], del[k] = 1;
            if (gcd(A[j], A[nxt[j]]) == 1)
                Q.push(P(j, nxt[j]));
        }
        printf("%d", (int)res.size());
        for (auto x : res)
            printf(" %d", x);
        putchar('\n');
    }
    return 0;
}
E 补题

d p [ i ] dp[i] dp[i] 代表建筑 [ 1 , i ] [1,i] [1,i] 拍成照片可以取得的最大值, f ( i , j ) f(i,j) f(i,j) 代表将建筑 [ i , j ] [i,j] [i,j] 拍成照片获得的值。枚举最后一张照片的左边界,则有递推 d p [ i ] = max ⁡ 0 ≤ j < i { d p [ j ] + f ( j + 1 , i ) } dp[i]=\max\limits_{0\leq j<i}\{dp[j]+f(j+1,i)\} dp[i]=0j<imax{ dp[j]+f(j+1,i)} 时间复杂度 O ( N 2 ) O(N^2) O(N2),显然难以胜任。照片值取 h i h_i hi 最小的元素的 b i b_i bi,那么设 j j j 为最大的满足 j < i , h j < h i j<i,h_j<h_i j<i,hj<hi 的元素,此时有 h j < min ⁡ j < k ≤ i h k ,   h i < min ⁡ j < k < i h k h_j<\min\limits_{j<k\leq i}h_k,\ h_i<\min\limits_{j<k<i}h_k hj<j<kiminhk, hi<j<k<iminhk 若将 i , j i,j i,j 置于同一张照片,则求解 d p [ i ] dp[i] dp[i] 等效于求 d p [ j ] dp[j] dp[j];反之,取 max ⁡ j ≤ k < i d p [ k ] + b i \max\limits_{j\leq k<i}dp[k]+b_i jk<imaxdp[k]+bi

使用单调递增栈求解 j j j,观察到需要维护的值为栈顶节点 j j j 至当前元素 i i i d p [ k ] ( j ≤ k < i ) dp[k](j\leq k<i) dp[k](jk<i) 的最大值,那么在维护单调栈的同时合并栈顶节点的信息即可。总时间复杂度 O ( N ) O(N) O(N)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 300005;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int N, H[maxn], B[maxn];
int top, st[maxn], id[maxn];
ll dp[maxn];

int main()
{
    
    
    scanf("%d", &N);
    for (int i = 1; i <= N; ++i)
        scanf("%d", H + i);
    for (int i = 1; i <= N; ++i)
        scanf("%d", B + i);
    dp[0] = id[0] = 0, st[++top] = 0;
    for (int i = 1; i <= N; ++i)
    {
    
    
        dp[i] = -inf;
        while (top && H[st[top]] > H[i])
        {
    
    
            if (top - 1 && dp[id[st[top]]] > dp[id[st[top - 1]]])
                id[st[top - 1]] = id[st[top]];
            --top;
        }
        if (top > 1)
            dp[i] = max(dp[i], dp[st[top]]);
        dp[i] = max(dp[i], dp[id[st[top]]] + B[i]);
        st[++top] = i, id[i] = i;
    }
    printf("%lld\n", dp[N]);
    return 0;
}
F 补题

F l o y d Floyd Floyd 求解任意两点间最短路;枚举每一条边 ( x , y , e s ( x , y ) ) (x,y,es(x,y)) (x,y,es(x,y)),遍历三元组 ( u , v , l ) (u,v,l) (u,v,l) 及其反向路径,若满足下述条件,则将对应边计入答案
d i s ( u , x ) + e s ( x , y ) + d i s ( y , v ) ≤ l dis(u,x)+es(x,y)+dis(y,v)\leq l dis(u,x)+es(x,y)+dis(y,v)l Q , M Q,M Q,M 的量级看做 N 2 N^2 N2,总时间复杂度 O ( N 4 ) O(N^4) O(N4),显然难以胜任。将不等式变形为 e s ( x , y ) + d i s ( y , v ) ≤ l − d i s ( u , x ) es(x,y)+dis(y,v)\leq l-dis(u,x) es(x,y)+dis(y,v)ldis(u,x) 此时左式有 3 3 3 个变量,右式有 3 3 3 个变量。枚举边 ( x , y ) (x,y) (x,y),对于每一个节点 v v v,若存在左式小于等于右式最大值的情况,则至少有一个三元组 ( u , v , l ) (u,v,l) (u,v,l) 满足条件,则这条边是有用边。

具体而言,枚举左右两边同时出现的变量 x x x;枚举 v , u v,u v,u ,求解右式的最大值 m x [ v ] mx[v] mx[v](其值与 y y y 无关),然后枚举 y , v y,v y,v,判断边 ( x , y ) (x,y) (x,y) 是否为有用边。总时间复杂度 O ( N 3 ) O(N^3) O(N3)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 605;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int N, M, Q, es[maxn][maxn], qs[maxn][maxn];
ll ds[maxn][maxn], mx[maxn];

int main()
{
    
    
    scanf("%d%d", &N, &M);
    memset(ds, 0x3f, sizeof(ds));
    for (int i = 1; i <= N; ++i)
        ds[i][i] = 0;
    for (int i = 1, x, y, z; i <= M; ++i)
    {
    
    
        scanf("%d%d%d", &x, &y, &z);
        es[x][y] = es[y][x] = z, ds[x][y] = ds[y][x] = z;
    }
    scanf("%d", &Q);
    for (int i = 1, x, y, z; i <= Q; ++i)
    {
    
    
        scanf("%d%d%d", &x, &y, &z);
        qs[x][y] = qs[y][x] = z;
    }
    for (int k = 1; k <= N; ++k)
        for (int i = 1; i <= N; ++i)
            for (int j = 1; j <= N; ++j)
                ds[i][j] = min(ds[i][j], ds[i][k] + ds[k][j]);
    int res = 0;
    for (int x = 1; x <= N; ++x)
    {
    
    
        for (int v = 1; v <= N; ++v)
        {
    
    
            mx[v] = -inf;
            for (int u = 1; u <= N; ++u)
                if (qs[v][u])
                    mx[v] = max(mx[v], qs[v][u] - ds[u][x]);
        }
        for (int y = 1; y < x; ++y)
            if (es[x][y])
                for (int v = 1; v <= N; ++v)
                    if (es[x][y] + ds[y][v] <= mx[v])
                    {
    
    
                        ++res;
                        break;
                    }
    }
    printf("%d\n", res);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/neweryyy/article/details/115067661