【解题总结】SWERC 2019(Codeforces Gym 102501)

我解决的:B(1 WA)、I、J、A(3 WA)、D(1 WA,1 TLE)。

没看的:无。

旁观的:C、F、K、G、H。

看了但没做出来的:E、L。

I Rats

简单题,略。

B Biodiversity

简单题,略。

C Ants

简单题,略。

A Environment-Friendly Travel

最短路,略。本来应该一次过的题目,但写错了好几个地方,简直丢人。

F Icebergs

题意:给定几个简单多边形,计算其面积的和。

计算几何模板题,丢人的在于我还不会写(板子也没带)。

D Gnalcats

题意:有一些可以对栈进行的操作。栈中的元素有两种:不可分割,称为简单类型(Simple);由两个元素构成的二元组,称为复杂类型(Complex)。给定两个操作序列,问对于任意一个只包含简单类型元素的足够长的栈,这两个操作序列是否等价。等价定义为:要么经两个操作序列分别操作后,得到的两个结果相等,要么两个操作序列在该栈上均失败。

本题就是模拟,先构造一个比较长的栈,每个元素有一个不同的编号,然后两个操作序列分别模拟即可。

最后判断二元组相等的时候要用一些 Hash,否则会 TLE。

K Birdwatching

题意:给定一张有向图和一个点 T T T,问有哪些点满足其有一条边连向 T T T,且其只能经这条边到达 T T T

我们先取出那些有一条边连向 T T T 的点,称为关键点。显然答案是其的子集。

随后扔掉关键点指向 T T T 的边,再构建反图。我们希望对每一个关键点,看看是否有另一个关键点到它的路径。

为此,我们可以对每一个关键点 u u u 维护一个集合 S ( u ) S(u) S(u),表示可以到达 u u u 的关键点的集合。我们保证这个集合大小不超过 2,因为只要达到了 2 就表明 u u u 不是答案了。

随后以每一个关键点为起点 DFS 即可。时间复杂度除了最后排序外为线性。

#include 
using namespace std;
int read(){
    int f = 1, x = 0;
    char c = getchar();
    while (c < '0' || c > '9'){if(c == '-') f = -f; c = getchar();}
    while (c >= '0' && c <= '9')x = x * 10 + c - '0', c = getchar();
    return f * x; 
}
int n, m, T;
int to[100005], nxt[100005], at[100005] = {0}, cnt = 0;
vector<int> keyp, ans;
int S[100005][2] = {0};
void dfs(int cur, int st){
    if (S[cur][1] || S[cur][0] == st || S[cur][1] == st)
        return ;
    if (!S[cur][0]) S[cur][0] = st;
    else S[cur][1] = st;
    for (int i = at[cur]; i; i = nxt[i])
        dfs(to[i], st);
}
int main(){
    n = read(), m = read(), T = read() + 1;
    for (int i = 1; i <= m; ++i){
        int u = read() + 1, v = read() + 1;
        if (v == T) keyp.push_back(u);
        else {
            to[++cnt] = u, nxt[cnt] = at[v], at[v] = cnt;
        } 
    }
    for (int x: keyp) dfs(x, x);
    
    for (int x: keyp){
        if (!S[x][1]) ans.push_back(x);
    }
    printf("%u\n", ans.size());
    sort(ans.begin(), ans.end());
    for (int x: ans)
        printf("%d\n", x - 1);
    return 0;
}

J Counting Trees

题意:给定一个二叉树关于点权的中序遍历,满足每一个点的点权均不小于父亲节点,问具有这种中序遍历的不同形态的二叉树有多少个。

看样例 1,可以看出,如果点权互不相同,那么结果是唯一确定的。因为每次可以找到最小权的点,其一定是当前子树的根,然后在其左右递归下去即可。

那么如果有多个最小权的节点呢?显然,它们应该是连在一起的,否则就破坏了题目的条件。如果这些节点有 t t t 个,那么这些节点形成的树的数目就是 Catalan ⁡ t \operatorname{Catalan}_t Catalant。而对于被最小权节点分割开的区间,它们的答案只要在此基础上乘起来就行了。

这种分治的做法很直观,但是时间复杂度难以降下来。实际上可以用一些单调栈的做法,考虑每一个点权之前,离其最近的、不大于它的点权。然后考虑后者是否将前者隔断。具体可以看代码理解。

#include 
#define MOD 1000000007
using namespace std;
int read(){
    int f = 1, x = 0;
    char c = getchar();
    while (c < '0' || c > '9'){if(c == '-') f = -f; c = getchar();}
    while (c >= '0' && c <= '9')x = x * 10 + c - '0', c = getchar();
    return f * x; 
}
int n, a[1000005], inv[1000005], catalan[1000005];
int st[1000005], top, lft[1000005], cnt[1000005] = {0};
void init(){
    n = read();
    for (int i = 1; i <= n; ++i)
        a[i] = read();
    inv[1] = 1;
    for (int i = 2; i <= n + 1; ++i)
        inv[i] = 1ll * (MOD - MOD / i) * inv[MOD % i] % MOD;
    catalan[0] = catalan[1] = 1;
    for (int i = 2; i <= n; ++i){
        catalan[i] = 1ll * catalan[i - 1] * inv[i] % MOD;
        catalan[i] = 1ll * catalan[i] * (2 * i - 1) % MOD;
        catalan[i] = 1ll * catalan[i] * (2 * i) % MOD;
        catalan[i] = 1ll * catalan[i] * inv[i + 1] % MOD;
    }
}
void solve(){
    a[0] = -1;
    st[top = 1] = 0;
    for (int i = 1; i <= n; ++i){
        while (a[st[top]] > a[i])
            --top;
        lft[i] = st[top];
        st[++top] = i;
    }
    
    // 用 cnt 计算位于同一个子树内的相同点权个数
    int res = 1;
    for (int i = 1; i <= n; ++i){
        if (a[lft[i]] < a[i]){
            res = 1ll * res * catalan[cnt[a[i]]] % MOD;
            cnt[a[i]] = 1;
        }else{
            ++cnt[a[i]];
        }
    }
    for (int i = 0; i <= 1000000; ++i)
        res = 1ll * res * catalan[cnt[i]] % MOD;
    printf("%d\n", res);
}
int main(){
    init();
    solve();
    return 0;
}

H Pseudo-Random Number Generator

题意:给了一个神秘的随机数生成器,求前 N N N 个数中有多少是偶数。

由抽屉原理,必然存在一个循环节。可以先用 Floyd 判圈算法算出进入循环节前要有多少个数,以及循环节有多长。然后每 1 0 7 10^7 107 个数打一个表,暴力计算即可。

G Swapping Places

题意:有 S S S 种动物,有 L L L 个不同种动物间的友好关系(没有传递性)。给定一个长为 N N N 的动物队列,相邻的动物如果有友好关系就可以交换位置。问可以得到的字典序最小的队列。

由于关系无传递性,不能用并查集维护。

注意到位置 i i i j j j 的动物不可交换,且 i < j i < j i<j,那么无论如何, i i i 都会在 j j j 前面。因此可以连一条 i i i j j j 的边,然后跑一个字典序最小的拓扑排序即可。

问题在于这样边的数目会很多,是 O ( N 2 ) O(N^2) O(N2) 的。我们改进一下,对于每一个位置 j j j,如果前面有不可与其交换的动物,那就让最靠后的那个连向 j j j。于是边数下降到了 O ( N S ) O(NS) O(NS),可以通过。

总时间复杂度为 O ( N S + N log ⁡ N ) O(NS + N \log N) O(NS+NlogN)

#include 
using namespace std;
int S, L, N;
string s1, s2;
vector<string> vec;
unordered_map<string, int> mp;
bool ok[205][205] = {0};
int que[100005], last_pos[205] = {0};
vector<int> G[100005];
int du[100005] = {0}, ans[100005];
priority_queue<pair<int, int>, vector<pair<int, int> >, 
    greater<pair<int, int> > > pq;
void init(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> S >> L >> N;
    for (int i = 1; i <= S; ++i){
        cin >> s1;
        vec.push_back(s1);
    }
    sort(vec.begin(), vec.end());
    for (int i = 1; i <= S; ++i){
        mp[vec[i - 1]] = i;
    }
    for (int i = 1; i <= L; ++i){
        cin >> s1 >> s2;
        int u = mp[s1], v = mp[s2];
        ok[u][v] = ok[v][u] = true;
    }
    for (int i = 1; i <= N; ++i){
        cin >> s1;
        int id = mp[s1];
        que[i] = id;
        for (int j = 1; j <= S; ++j){
            if (!ok[j][id] && last_pos[j]){
                G[last_pos[j]].push_back(i);
                ++du[i];
            }
        }
        last_pos[id] = i;
        if (!du[i]) pq.push(make_pair(id, i));
    }
}
void solve(){
    int tot = 0;
    while (!pq.empty()){
        int h = pq.top().second;
        pq.pop();
        ans[++tot] = h;
        for (int v: G[h]){
            --du[v];
            if (!du[v]) pq.push(make_pair(que[v], v));
        }
    }
    for (int i = 1; i < N; ++i)
        cout << vec[que[ans[i]] - 1] << ' ';
    cout << vec[que[ans[N]] - 1] << '\n';
}
int main(){
    init();
    solve();
    return 0;
}

E Pixels

待补。。。

L River Game

待补。。。

你可能感兴趣的:(解题总结)