Codeforces Round #686 (Div. 3)

本文解析了Codeforces平台上的六道算法题目,从Special Permutation到Array Partition,涵盖数据结构、算法设计与实现等方面,提供了清晰的思路和示例代码。

Codeforces Round #686 (Div. 3)

A. Special Permutation

题意: 找到一个排列第i位置不等于i

题解: 那么输出i+1,最后输出1就行

代码:

// 2 1
// 2 1 5 3 4
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
int const N = 2e5 + 10;
int n, m, T;

int main() {
    cin >> T;
    while(T--) {
        cin >> n;
        for (int i = 1; i <= n; ++i) {
            int tmp = (i + 1) % n;
            if (tmp == 0) tmp = n;
            cout << tmp << " ";
        }
        cout << endl;
    }
    return 0;
}

B. Special Permutation

题意: 找到仅仅出现一次且值最小的元素的下标

题解: 扫两遍就行

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
int const N = 2e5 + 10;
int n, m, T;
int st[N], a[N], idx[N];

int main() {
    cin >> T;
    while(T--) {
        memset(st, 0, sizeof st);
        memset(idx, 0, sizeof idx);
        cin >> n;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            idx[a[i]] = i;
            st[a[i]]++;
        }
        int flg = 0;
        for (int i = 1; i <= n; ++i) {
            if (st[i] == 1) {
                flg = 1;
                cout << idx[i] << endl;
                break;
            } 
        }
        if (!flg) cout << -1 << endl;
    }
    return 0;
}

C. Sequence Transformation

题意: 要求选定一个x,每次操作可以删除一段不包含x的区间,问如何选择x,能够是的删除次数最少,然后整个数列都为x,打印最少操作次数

题解: 先把相邻相等的元素删掉,然后计算每个元素可以把区间划分为几段,取个min即可。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
int const N = 2e5 + 10;
int n, m, T;
int cnt[N];

int main() {
    cin >> T;
    while(T--) {
        memset(cnt, 0, sizeof cnt);
        cin >> n;
        int last = -1;
        vector<int> a;
        for (int i = 1; i <= n; ++i) {
            int t;
            cin >> t;
            if (t != last) a.push_back(t), last = t;
        }
        // for (auto ai: a) cout << ai << " " ;
        // cout << endl;
        for (auto ai: a) cnt[ai]++;
        int res = 1e9 + 10;
        for (int i = 1; i <= n; ++i) {
            if (!cnt[i]) continue;
            cnt[i] += 1;
            if (i == a.back()) cnt[i] -= 1;
            if (i == a.front()) cnt[i] -= 1;
            res = min(res, cnt[i]);
        }
        printf("%d\n", max(res, 0));
    }
    return 0;
}

D. Number into Sequence

题意: 给定一个数字,问将该数字变为几个数字的累成的最长长度是多少,转换为的序列前一个必须为后一个的约数

题解: 分解质因数,然后答案序列必然为最多出现的那个的幂次减一,其他的累成起来。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

int const N = 2e6 + 10;
int n, m, T;
int prime[N], cnt;
int st[N];

void get_prime(int n) {
    for (int i = 2; i <= n; ++i) {
        if (!st[i]) prime[cnt++] = i; // 如果这个数字没有被记录,那么这个数字必然为素数,记录一下
        for (int j = 0; prime[j] <= n/i; ++j) {
            st[prime[j] * i] = true;  // 筛掉pj*i这个合数
            if (i % prime[j] == 0)  break; // i%pj==0,说明pj是i的最小素因子,因此i*素数的最小素因子也是pj,在i递增的时候也会被筛掉,因此不需要在这里判断
        }                
    }
}

LL qmi(LL a, LL k) {
    LL res = 1 ;  // res记录答案, 模上p是为了防止k为0,p为1的特殊情况
    while(k) {  // 只要还有剩下位数
        if (k & 1) res = (LL)res * a ;  // 判断最后一位是否为1,如果为1就乘上a,模上p, 乘法时可能爆int,所以变成long long
        k >>= 1;  // 右移一位
        a = (LL) a * a ;  // 当前a等于上一次的a平方,取模,平方时可能爆int,所以变成long long
    }
    return res;
}

int main() {
    // freopen("in.txt", "r", stdin);
    get_prime(N - 1);
    cin >> T;
    while(T--) {
        LL x;
        scanf("%lld", &x);
        LL backup = x;
        LL res = 0, res_val = 0;
        LL remian = 1;
        for (int i = 0; prime[i] <= x / prime[i]; ++i) {
            int p = prime[i];
            if (x % p == 0) {
                int s = 0;
                while (x % p == 0) {
                    s++;
                    x /= p;
                }
                if ((LL)s > res) {
                    res = (LL)s;
                    res_val = p;
                }
            }
        }
        if (x > 1) {
            if ((LL)1 > res) {
                res = 1;
                res_val = x;
            }
        }
        // cout << res << " " << res_val << endl;
        cout << res << endl;
        if (res == 1) printf("%lld\n", backup);
        else {
            for (int i = 1; i <= res - 1; ++i) printf("%lld ", res_val);
            printf("%lld\n", backup / qmi(res_val, res - 1));
        }
    }
    return 0;
}

/*
1
2 
3
2 2 90 
1
4999999937 
1
4998207083 
*/

E. Number of Simple Paths

题意: 给定一张n个点n条边的无向图,问这张无向图中有多少条简单路。 1 < = n < = 1 0 5 1<=n<=10^5 1<=n<=105

题解: n个点n条边表示存在一个环,那么无向图就会变成基环树。那么所有的简单路的两个端点就是基环树的支链内的两个点或者任意两个位于不同支链的点。一开始认为任意2个点都会形成2条简单路,那么简单路就是n * (n - 1),然后对于一条支链内的任意2个点,因为是一棵树,因此任意2个点只会有1条简单路,那么减去这些多算的简单路即可。所以可以先使用tarjan算法进行找环,然后以环为根做dfs计算子树大小。

代码:

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N = 2e5 + 10, M = N * 2;

int n, m;
int h1[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;  // dfn记录搜索顺序,low记录回溯值(向上能够得到的最小值), timestamp记录当前搜索到的节点编号
int stk[N], top;  // stk存放dcc内的点,一个栈里的所有点属于一个dcc
int dcc[N], dccnum;  // dcc[i] = j表示i点的dcc编号为j,dccnum表示dcc的数目
int vis[N];     

void add(int a, int b, int h[]) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

// u:当前点,from:来的那条边
// 该算法求出桥和边dcc
void tarjan(int u, int from, int h[]) {
    dfn[u] = low[u] = ++ timestamp;  // 记录dfn和low
    stk[ ++ top] = u;

    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (!dfn[j]) {// 没有走过
            tarjan(j, i, h);
            low[u] = min(low[u], low[j]);
        }
        else if (i != (from ^ 1))  // 判掉了父节点和重边
            low[u] = min(low[u], dfn[j]);
    }

    // 以下为求边双模板
    if (dfn[u] == low[u]) {  // 如果是一个dcc的话,出栈
        ++ dccnum;
        int y;
        do {
            y = stk[top -- ];
            dcc[y] = dccnum;
        } while (y != u);
    }
}

LL dfs(int u, int fa) {
    LL sum = 1;
    for (int i = h1[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa || vis[dcc[j]] >= 2) continue;
        sum += dfs(j, u);
    }
    return sum;
}

int main() {
    int T;
    cin >> T;
    while(T--) {
        cin >> n;
        idx = dccnum = timestamp = 0;
        memset(h1, -1, sizeof h1);
        for (int i = 1; i <= n; ++i) {
            dfn[i] = vis[i] = low[i] = 0;
            int a, b;
            scanf("%d%d", &a, &b);
            add(a, b, h1), add(b, a, h1);
        }

        tarjan(1, -1, h1);

        LL res = (n - 1) * (LL)n;  // 总的路数目,任意2个点认为有2条路,但是有的点之间只有一条路
        vector<int> root;
        int rt = 1;
        for (int i = 1; i <= n; ++i) {
            vis[dcc[i]] ++;
            if (vis[dcc[i]] >= 2) rt = dcc[i];  // 找到根的dcc标号
        }
        for (int i = 1; i <= n; ++i) 
            if (dcc[i] == rt) {  // 是根的话
                LL tmp = dfs(i, -1);  
                res -= (tmp - 1) * tmp / 2;  // 减掉同一颗子树内的简单路,因为同一颗子树中的路只有一条
            }

        printf("%lld\n", res);
    }

    return 0;
}

F. Array Partition

题意: 给定一个长度为n的数列,问是否能够将数列划分为3段,是的第一段的最大值=第二段的最小值=第三段的最大值,数列长度为1e5,数列中元素的大小为1e9

题解: 可以知道能够满足用来做最大值、最小值的元素必然出现了最少3次,因此可以考虑枚举这些元素。那么对于每个元素我们就需要找到包含它且它为最小值、最大值的区间,然后我们只需要判断该数字的某3个区间是否能够覆盖整个数列即可。那么怎么求包含某个数字的最小值、最大值的区间呢?只需要单调栈扫描四遍即可。

代码:


#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 2e5 + 10;

LL n,m,p;
LL num[N];
vector<int>v[N];
vector<int>g;
int premi[N],curmi[N],premx[N],latemx[N];
int st[N];
int nn;

int getid(LL x){
    return lower_bound(g.begin(),g.end(),x) - g.begin() + 1;
}
int main(){
    int T;scanf("%d",&T);
    while(T--){
        scanf("%d", &n);
        for(int i=1;i<=n;i++) v[i].clear();
        g.clear();

        // 去重、离散化
        for(int i=1;i<=n;i++){
            scanf("%lld", &num[i]);
            g.push_back(num[i]);
        }
        sort(g.begin(),g.end());
        g.erase((unique(g.begin(),g.end())),g.end());
        nn = g.size();
        for(int i=1;i<=n;i++) num[i] = getid(num[i]);

        // 4次单调栈扫描得到每个数字左边比它小的、比它大的,右边比它小的、比它大的最边界的下标
        int s = 0;
        st[0] = 0;
        for(int i=1;i<=n;i++){
            while(s && num[i]>=num[st[s]]) s--;
            premx[i] = st[s]+1;  // 记录左边连续比他小的最后一个数字
            st[++s] = i;
        }
        s = 0;
        st[0] = n+1;
        for(int i=n;i>=1;i--){
            while(s && num[i]>=num[st[s]]) s--;
            latemx[i] = st[s]-1;  // 记录右边连续比他小的最后一个数字
            st[++s] = i;
        }
        s = 0;
        st[0] = 0;
        for(int i=1;i<=n;i++){
            while(s && num[i]<=num[st[s]]) s--;
            premi[i] = st[s]+1;  // 记录左边连续比他小大的最后一个数字
            st[++s] = i;
        }
        s = 0;
        st[0] = n+1;
        for(int i=n;i>=1;i--){
            while(s && num[i]<=num[st[s]]) s--;
            curmi[i] = st[s]-1;   // 记录右边连续比他大的最后一个数字
            st[++s] = i;
        }

        // 记录每个数字的所有出现位置
        for(int i=1;i<=n;i++) v[num[i]].push_back(i);
        int f = 0;

        // 对于每个满足条件的数字(出现3次以上),需要选出3个看区间能够覆盖整个[1, n]
        for(int i=1;i<=n;i++){
            int sz = v[i].size();
            if(sz<3) continue;  // 不符合条件
            if(premx[v[i][0]]==1 && latemx[v[i][sz-1]] == n){
                for(int k=1;k<sz-1;k++){  // 枚举中间的那个数字
                    int id = v[i][k];
                    if(premi[id]<=latemx[v[i][0]]+1&&curmi[id]>=premx[v[i][sz-1]]-1){  // 如果能够把区间覆盖
                        f = 1;
                        printf("YES\n");
                        int r = max(id+1,premx[v[i][sz-1]]);
                        LL resc = n-r+1;
                        int l = min(id-1,latemx[v[i][0]]);
                        LL resa = l;
                        printf("%lld %lld %lld\n",resa,n-resa-resc,resc);
                        break;
                    }
                }
            }
            if(f) break;
        }
        if(!f) printf("NO\n");
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值