2022-04-20每日刷题打卡

2022-04-20每日刷题打卡

代码源——每日一题

最长同余子数组 - 题目 - Daimayuan Online Judge

给定一个 N 长数组 {A}, 元素之间 两两不同.

现在要求你找出最长的 连续子序列, 使得这些元素 (modM) 意义下同余, 其中 M≥2.

形式化的说, 在数组中找到最长的 A[L…R],∃M≥2, 使得:

AL≡AL+1≡AL+2≡⋯≡AR(modM)
其中, a≡b(modM) 即是说 a%M=b%M
输出此长度即可.

数据规模

1≤n≤2×10^3
1≤ai≤10^18
输入格式
第一行一个数字 N。

接下来一行 N 个整数 A1,A2,…,AN。

输出格式

一个数,表示最长连续同余子序列的长度。

样例输入

4
8 2 10 5

样例输出

3
注意到 8,2,10 均为偶数.

bonus1: consider what if N is greater (even 1≤N≤2×105)?

bonus2: consider how to solve the ‘subsequence’ version.

这题如果数据是1e3的话完全可以暴力求解。

首先,怎么知道一个数组是否同余呢?当然是a1%ma2%m……==an%m。换句话说,他们的之间的差值,是可以被一个数整除的。

比如1 5 7,差值是4 2,可以被2整除,这就是个同余数组了。这属于是一个结论记住就行。

那么我们可不可以这么想,我们先把所有数的相邻差值算出来,然后看有多长的差值数组都能被一个数整除,那就可以知道最长连续同余子序列的长度了。你问咋知道能不能被一个数整除?那当然是计算他们的最大公约数了啊,不过要注意,这个最大公约数不能是1(题目要求大于等于2),然后此时问题终于被我转换成了:找一个最长的差值数组,他们的最大公约数大于1。

我们可以滑动窗口跑差值数组,每次计算当前滑动窗口的gcd,如果大于1,则右端向前走,反之左端向前走。我们在这过程记录滑动窗口的最大长度就行。每次算一下滑动窗口内部的gcd,最后算下来总过程复杂度是n^2logn,是可以过的。不过有一个要注意的点,用我这个方法会有个问题,当我们的相邻值相等时,当然也该算同余,但是相等就说明差值为0,那么gcd当然不可能大于1,所以相等的这一段可能被我们忽视掉,我们应该加上一个回头的过程,当计算完一个区间的gcd后,我们回头计算一下上一个元素和滑动窗口里的元素是否模去gcd后相等,如果相等那么这个元素也该算在同于子序列里,我们就这么计算,算到模去gcd后不相等即可。

要写就写难的,写题不就是锻炼自己。

2e5数据的情况下显然是不可以过的,那么我们该怎么优化呢?滑动窗口的n复杂度肯定不能优化掉了,那我们就从计算区间的gcd入手,怎么把计算区间的gcd从nlogn降下来?说到区间查询问题。当然是我们的线段树了,只要准备一个线段树,每个节点存的是当前区间的gcd即可,然后我们每次查询区间的gcd时间复杂度也才logn,这样总共过程就是nlogn了,2e5也能过。

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>

#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 100010;
ll a[N], f[4 * N];

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

void buildtree(ll k, ll l, ll r)
{
    if (l == r)
    {
        f[k] = a[l];
        return;
    }
    int m = (l + r) / 2;
    buildtree(k + k, l, m);
    buildtree(k + k + 1, m + 1, r);
    f[k] = gcd(f[k + k], f[k + k + 1]);
}

ll calc(ll k, ll l, ll r, ll x, ll y)
{
    if (l == x&&r==y)return f[k];

    int m = (l + r) / 2;
    if (y <= m)return calc(k + k, l, m, x, y);
    else
        if (x > m)return calc(k + k + 1, m + 1, r, x, y);
        else
        {
            return gcd(calc(k + k, l, m, x, m), calc(k + k + 1, m + 1, r, m + 1, y));
        }
}

int main()
{
    cin.sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, t;
    cin >> n;
    vector<ll>v(n + 1);
    for (int i = 1; i <= n; i++)cin >> v[i];
    for (int i = 1; i <= n; i++)
    {
        a[i] = abs(v[i] - v[i - 1]);
    }
    
    buildtree(1, 1, n);
    ll l = 1, r = 1, len = 1, ans = 1;
    while (r <= n)
    {
        ll num = calc(1, 1, n, l, r);
        if (num > 1)r++;
        else l++;
        while (l > r)r++;
        ans = l;
        while (num>1&&ans <= n && ans > 1 && v[ans] % num == v[ans - 1] % num)
        {
            ans--;
        }
        if (r - ans > len)
        {
            len = r - ans;
        }
    }
    
    cout << len;
    return 0;
}

CodeForces——线段树专题

B - Segment Tree, part 2 - B. Inverse and K-th one

There is an array of n booleans, initially filled with zeros. You need to write a data structure that processes two types of queries:

change the value of all elements on a segment from l to r−1 to the opposite,
find the index of the k-th one.

Input

The first line contains two numbers n and m (1≤n,m≤100000), the size of the array and the number of operations. The following lines contain the description of the operations. The description of each operation is as follows:

1 l r: change the value of all elements on a segment from l to r−1 to the opposite (0≤l<r≤n),
2 k: the index of the k-th one (ones are numbered from 0, it is guaranteed that there are enough ones in the array).
Output
For each operation of the second type, print the index of the corresponding one (all indices in this problem are from 0).

Example

input

5 7
1 1 3
2 1
1 0 2
2 0
2 1
1 0 5
2 2

outpu

2
0
2
4

这题是说,一开始给你一个数组全是0,有两种操作,一个是修改一个区间里的数组状态,1变0,0变1。一个是让你找到第k个1的位置。

建线段树,每个节点存的是当前区间的区间和,我们把lz标记变成bool类型,当lz为true时,我们就把当前节点的值修改并把标记下传。怎么修改?我们修改数值是把0变成1,1变成0,我们节点计算的是区间和,那么变化后的区间和+变化前的区间和应该=区间长度。

然后至于询问,我们每次根据两个子节点的值判断该去左边还是右边:当左边的区间和大于等于k时,说明第k个1应该在左节点;反之我们去右边找,同时把k减去左节点的值。

(这题有点问题,他说第k个1,但是k的范围是0n-1的,为了方便我们还是改成1n比较好)

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>

#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 100010;
ll a[N], f[4 * N];
bool b[4 * N];

void revise(ll k, ll l, ll r, ll x, ll y)
{
    if (l == x && r == y)
    {
        f[k] = r - l - f[k] + 1;
        b[k] = !b[k];
        return;
    }
    int m = (l + r) / 2;
    if (b[k])
    {
        b[k + k] = !b[k + k];
        f[k + k] = (m - l + 1) - f[k + k];
        b[k + k + 1] = !b[k + k + 1];
        f[k + k + 1] = (r - m) - f[k + k + 1];
        b[k] = false;
    }
    if (y <= m)revise(k + k, l, m, x, y);
    else
        if (x > m)revise(k + k + 1, m + 1, r, x, y);
        else
        {
            revise(k + k, l, m, x, m);
            revise(k + k + 1, m + 1, r, m + 1, y);
        }
    f[k] = f[k + k] + f[k + k + 1];
}

ll calc(ll k, ll l, ll r, ll x)
{
    if (l == r)return l;

    int m = (l + r) / 2;
    if (b[k])
    {
        b[k + k] = !b[k + k];
        f[k + k] = (m - l + 1) - f[k + k];
        b[k + k + 1] = !b[k + k + 1];
        f[k + k + 1] = (r - m) - f[k + k + 1];
        b[k] = false;
    }
    ll res = -1;
    if (f[k + k] >= x)res = calc(k + k, l, m, x);
    else res = calc(k + k + 1, m + 1, r, x - f[k + k]);
    f[k] = f[k + k] + f[k + k + 1];
    return res;
}

int main()
{
    cin.sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    memset(b, false, sizeof b);
    int n, t;
    cin >> n >> t;
    while (t--)
    {
        int st, x, y;
        cin >> st >> x;
        if (st == 1)
        {
            cin >> y;
            revise(1, 1, n, x + 1, y);
        }
        else
        {
            cout << calc(1, 1, n, x + 1) - 1 << endl;
        }
    }
    return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue.js是一种流行的JavaScript框架,用于构建用户界面。要实现每日打功能,你可以按照以下步骤进行: 1. 创建一个Vue组件,用于显示打相关的信息和按钮。 2. 在组件的data属性中定义一个变量,用于记录打状态,比如isPunched。 3. 在组件的methods属性中定义一个方法,用于处理打逻辑。当用户点击打按钮时,该方法会被调用。 4. 在方法中,你可以使用JavaScript的Date对象获取当前日期,并将其与用户上次打的日期进行比较。如果日期不一致,表示用户需要进行打操作。 5. 如果需要保存用户的打记录,你可以使用浏览器的本地存储(localStorage)或者发送请求到服务器来保存数据。 下面是一个简单的示例代码: ```html <template> <div> <p v-if="isPunched">今日已打</p> <p v-else>今日未打</p> <button @click="punch">打</button> </div> </template> <script> export default { data() { return { isPunched: false }; }, methods: { punch() { const lastPunchDate = localStorage.getItem('lastPunchDate'); const currentDate = new Date().toLocaleDateString(); if (lastPunchDate !== currentDate) { // 执行打逻辑 // ... // 更新打状态和日期 this.isPunched = true; localStorage.setItem('lastPunchDate', currentDate); } } } }; </script> ``` 在上面的示例中,我们使用了Vue的条件渲染指令(v-if和v-else)来根据打状态显示不同的文本。当用户点击打按钮时,会调用punch方法进行打逻辑的处理。我们使用localStorage来保存用户的上次打日期,并在下次打时进行比较。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值