Codeforces Round #736 (Div. 2) (C~D)

本文解析了两道算法题目,一是通过有向图的出度判断可删除节点的数量,二是寻找数组中最长子数组,使得该子数组的所有元素在除以某大于1的整数后余数相同。文章提供了详细的解题思路与代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C. Web of Lies

思路

  1. 这题我们只需要看出来,对于某个点 u,如果存在 v > u, 且 u->v 之间有边那么我们就一定可以把 u 删掉,翻译过来就是如果 u 的出度(我们默认两个节点之间的边是有向边,节点编号小的 -> 指向编号节点大的节点) > 0 , u 就可以被删去。
  2. 因为每次增加或者删去一条边,只会改变一个点的出度,那么我们就可不断的位数 每个节点的出度变化情况,就能快速的得出有多少个点被删掉,那么 n - 被删掉的点 ,就是答案了

代码

#include <bits/stdc++.h>
using namespace std;
#define db  double
#define ll  long long
#define Pir pair<int, int>
#define fi  first
#define se  second
#define pb  push_back
#define m_p make_pair
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
/*==========ACMer===========*/
const int N = 2e5 + 10;
int n, m;
int in[N], out[N];


int main()
{
    scanf("%d %d", &n, &m);
    int u, v;
    for (int i = 1; i <= m; i ++)
    {
        scanf("%d %d", &u, &v);
        if (u > v) swap(u, v);
        out[u] ++;
        in[v]  ++;
    }

    int ans = 0;
    for (int i = 1; i <= n; i ++)
    {
        if (out[i])
            ans ++;
    }

    int q; scanf("%d", &q);
    while (q --)
    {
        int op; scanf("%d", &op);
        if (op == 1)
        {
            scanf("%d %d", &u, &v);
            if (u > v) swap(u, v);
            if (out[u] == 0) ans ++;
            out[u] ++;
            in[v]  ++;
        }
        else if (op == 2)
        {
            scanf("%d %d", &u, &v);
            if (u > v) swap(u, v);
            if (out[u] == 1) ans --;
            out[u] --;
            in[v]  --;
        }
        else
        {
            printf("%d\n", n - ans);
        }
    }



    return 0;
}

D. Integers Have Friends

题意

  1. 给我们 n 个元素的数组 a [], 让我们找出最长的一段 [l, r],对于这一段存在一个整数 x,x > 1, 且这一段中的所有数取余 x 的余数两两相同。

思路

  1. 因为最长的连续的段 [l, r] 所有数取余 x 的余数两两相同,那么 [l, r] 之间相邻的两个数的差值的绝对值,形成的一个新的差值序列,这个差值训练必然存在一个 >1 公共因子。
  2. 因此我们可以将问题转化为:把 a [] 中相邻两个数做差,形成一个 新的数组 b [], b [i] = a [i + 1] - a [i] , i <= n - 1。对这个 b [] 数组找出一个最长的一段 [l,r] , 且这一段 gcd (b [l], b [l + 1]…b [r]) != 1,
  3. 怎么找这一段呢?我们可以用 rmq 去维护区间 gcd,然后 O (1) 去查询区间 gcd 的值,rmq 预处理复杂度为 n * log (n) , 又因为每次都要 gcd 运算,所以: rmq 预处理复杂度为 n * log (n) * log (n).
  4. 有了上面的基础:最简单的找到最长 [l, r] 的方法就是 二分区间长度 , 然后暴力 check 二分的长度就行了。
  5. 还有一种更巧妙的方法:用双指针,去 O (n) 找最长区间 [l, r] , 当双指针指向的区间的 gcd 不为 1 的时候,这个区间是不符合题意的,此时我们必然要移动 左指针 L, 否则的我们继续移动右指针 R 来扩展我们的符合题意的区间的长度,在每次双指针移动的时候都维护答案的最优值,就能得到答案了

代码(rmq + 双指针)

#include <bits/stdc++.h>
using namespace std;
#define db  double
#define ll  long long
#define Pir pair<int, int>
#define fi  first
#define se  second
#define pb  push_back
#define m_p make_pair
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
/*==========ACMer===========*/
const int N = 2e5 + 10;
ll n, a[N], b[N], dp[N][20];
int mm[N];

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

void init_rmq(int n)
{
    mm[0] = -1;
    for (int i = 1; i <= n; i ++)
    {
        dp[i][0] = b[i];
        mm[i] = ((i & (i - 1)) == 0) ? mm[i - 1] + 1 : mm[i - 1];
    }
    
    for (int j = 1; j <= mm[n]; j ++)
    {
        for (int i = 1; i + (1 << j) - 1 <= n; i ++)
            dp[i][j] = gcd(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
    }
}

ll rmq(int x, int y)
{
    int k = mm[y - x + 1];
    return gcd(dp[x][k], dp[y - (1 << k) + 1][k]);
}

int main()
{
    int T; scanf("%d", &T);
    while (T --)
    {
        scanf("%lld", &n);
        for (int i = 1; i <= n; i ++)
        {
            scanf("%lld", &a[i]);
        }

        for (int i = 1; i < n; i ++)
        {
            b[i] = abs(a[i] - a[i + 1]);
        }
        
        init_rmq(-- n);
        int ans = 0;
        int l = 1;
        for (int r = 1; r <= n; r ++)
        {
            while (l <= r && rmq(l, r) == 1) l ++;
            ans = max(ans, r - l + 1);
        }

        printf("%d\n", ans + 1);
    }




    return 0;
}

代码(rmq + 二分)

#include <bits/stdc++.h>
using namespace std;
#define db  double
#define ll  long long
#define Pir pair<int, int>
#define fi  first
#define se  second
#define pb  push_back
#define m_p make_pair
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
/*==========ACMer===========*/
const int N = 2e5 + 10;
ll n, a[N], b[N], dp[N][20];
int mm[N];

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

void init_rmq(int n)
{
    mm[0] = -1;
    for (int i = 1; i <= n; i ++)
    {
        dp[i][0] = b[i];
        mm[i] = ((i & (i - 1)) == 0) ? mm[i - 1] + 1 : mm[i - 1];
    }
    
    for (int j = 1; j <= mm[n]; j ++)
    {
        for (int i = 1; i + (1 << j) - 1 <= n; i ++)
            dp[i][j] = gcd(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
    }
}

ll rmq(int x, int y)
{
    int k = mm[y - x + 1];
    return gcd(dp[x][k], dp[y - (1 << k) + 1][k]);
}

int main()
{
    int T; scanf("%d", &T);
    while (T --)
    {
        scanf("%lld", &n);
        for (int i = 1; i <= n; i ++)
        {
            scanf("%lld", &a[i]);
        }

        for (int i = 1; i < n; i ++)
        {
            b[i] = abs(a[i] - a[i + 1]);
        }
        
        init_rmq(-- n);
        int l = 1, r = n, ans = 1;
        while (l <= r)
        {
            int md = (l + r) >> 1;
            int flag = 0;
            for (int i = 1; i <= n - md + 1; i ++)
            {
                if (rmq(i, i + md - 1) != 1)
                {
                    flag = 1;
                    break;
                }
            }

            if (flag)
            {
                ans = md + 1;
                l = md + 1;
            }
            else
            {
                r = md - 1;
            }
        }

        //要注意:当 n==1 的时时候 ans 的值至少为 1
        printf("%d\n", ans);
    }




    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值