代码源每日一题 div1 (401-407)

代码源每日一题 div1 (401-407)

蜗蜗的数列

[Link](蜗蜗的数列 - 题目 - Daimayuan Online Judge)

思路

  • 差分

​ 首先 c i = a i − b i c_i=a_i-b_i ci=aibi,等价于让我们求操作后 c i c_i ci均为零。对于区间操作我们倾向于改变成单点操作,这样复杂度就降下来了,观察斐波那契数列 f i = f i − 1 + f i − 2 f_i=f_{i-1}+f_{i-2} fi=fi1+fi2,每一项与前两项有关,我们设 d i = c i − c i − 1 − c i − 2 d_i=c_i-c_{i-1}-c_{i-2} di=cici1ci2,拿对 a a a进行区间操作来看,对 [ l , r ] [l,r] [l,r]进行斐波那契 d l = d l + f 1 d_l=d_l+f_1 dl=dl+f1,中间任意一项均会被消除,剩下的只有 r + 1 , r + 2 r+1,r+2 r+1,r+2这两项受到影响了,由于最终要让 c i c_i ci均为零等价于 d i d_i di均为零,因此记录一个 c n t cnt cnt为当前不为零的个数,动态的维护一下即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, q;
int a[N], b[N], c[N], d[N];
int cnt;
int f[N];
void modify(int v, LL x) {
    bool ok = false;
    if (d[v] == 0) ok = true;
    d[v] = ((d[v] + x) % m + m) % m;
    if (ok && d[v]) cnt ++;
    else if (!ok && !d[v]) cnt --;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> q >> m;
    for (int i = 1; i <= n; i ++) cin >> a[i]; 
    for (int i = 1; i <= n; i ++) cin >> b[i]; 
    for (int i = 1; i <= n; i ++) c[i] = a[i] - b[i];
    d[1] = c[1], d[2] = c[2] - c[1];
    for (int i = 3; i <= n; i ++) d[i] = ((c[i] - c[i - 1] - c[i - 2]) % m + m) % m;
    for (int i = 1; i <= n; i ++) 
        if (d[i]) cnt ++;

    f[1] = 1;
    for (int i = 2; i < N; i ++) f[i] = ((LL)f[i] + f[i - 1] + f[i - 2]) % m;
    
    while (q --) {
        char c[2];
        int l, r;
        cin >> c >> l >> r;
        if (*c == 'A') {
            modify(l, 1);
           if (r + 1 <= n) modify(r + 1, -(f[r - l + 1] + f[r - l]));
           if (r + 2 <= n) modify(r + 2, -f[r - l + 1]);
        }
        else {
            modify(l, -1);
            if (r + 1 <= n) modify(r + 1, f[r - l + 1] + f[r - l]);
            if (r + 2 <= n) modify(r + 2, f[r - l + 1]);
        }

        cout << (cnt ? "No" : "Yes") << '\n';
    }
    return 0;
}

最大公约数

[Link](最大公约数 - 题目 - Daimayuan Online Judge)

思路

  • 枚举,数论

我们发现顺着推不方便,是否有另一种方面的暴力一点的 c h e c k check check答案的方法? g c d = d gcd=d gcd=d分成了 k k k段,则 a 1 d + a 2 d + . . . + a k d = s u m → ( a 1 + a 2 + . . . + a k ) d = s u m a_1d+a_2d+...+a_kd=sum\to(a_1+a_2+...+a_k)d=sum a1d+a2d+...+akd=sum(a1+a2+...+ak)d=sum,发现我们的 g c d gcd gcd一定总和的约数,然后约数的个数是很少的,因此我们可以直接枚举约数,判断当前约数,可以将这个数组分成多少份。

​ 由于是 g c d gcd gcd因此能分成 k k k份的 d d d一定能分成 k − 1 k-1 k1,对于分成 k k k份的最大的 d d d我们记录一个后缀最大值即可。0

​ 对于一个环因为我们枚举的是总和的约数,所以我们只需要记录出现最多的 m o d   d mod \ d mod d的约数即可,因为 s i ≡ s j ( m o d   d ) s_i\equiv s_j (mod\ d) sisj(mod d),那么 s j − s i ≡ 0 ( m o d   d ) s_j-s_i\equiv 0(mod\ d) sjsi0(mod d),由于是环且枚举总和的约数所以最后首尾一定可以合成一个。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
#define int long long
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N], s[N];
int res[N];
void g(LL x) {
    map<int, int> mp;
    int mx = 0;
    for (int i = 1; i <= n; i ++) {
        mp[s[i] % x] ++;
        mx = max(mx, mp[s[i] % x]);
    }
    res[mx] = max((LL)res[mx], x);
}
signed main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i <= n; i ++)
        s[i] = a[i] + s[i - 1];

    for (LL i = 1; i <= s[n] / i; i ++) {
        if (s[n] % i == 0) 
            g(i), g(s[n] / i);        
    }

    for (int i = n; i; i --)
        res[i] = max(res[i], res[i + 1]);
    
    for (int i = 1; i <= n; i ++)
        cout << res[i] << '\n';    
}

平方计数

[Link](平方计数 - 题目 - Daimayuan Online Judge)

思路

  • 调和级数

​ 暴力肯定不行,化简一下式子 a i 2 + a j = k 2 → a j = ( k + a i ) ( k − a i ) a_i^2+a_j=k^2\to a_j=(k+a_i)(k-a_i) ai2+aj=k2aj=(k+ai)(kai),因此等价于找对于一个 a j a_j aj有多少个数满足是 a j a_j aj一对约数差的一半,对于每个数直接枚举约数 O ( n n ) O(n\sqrt n) O(nn )不太行,我们可以采用一个常用的倍数遍历小技巧,利用调和级数直接枚举每个约数和构成的数,然后乘法原理算一下贡献。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int cnt[1000010];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (int i = 1, x; i <= n; i ++)
        cin >> x, cnt[x] ++;
    
    int res = 0;
    for (int i = 1; i <= 1000000; i ++)
        for (int j = i; j <= 1000000; j += i) {
            int l = i, r = j / i;
            int d = abs(l - r);
            if (d % 2 == 0) {
                res += cnt[d / 2] * cnt[j];
            }
        }
        
    cout << res /  2 << '\n';
    return 0;
}

字典序最小

[Link](字典序最小 - 题目 - Daimayuan Online Judge)

思路

  • 贪心

​ 我们可以用一个栈动态的来维护这个序列,因为要字典序最小,仿照单调栈的思想,每进来一个数就看看能不能将前面比它小的数弹走,但是由于要让我们选够 n n n个数,即每个数都需要选一次,因此我们还要看一下如果将当前这个弹走,后面是否还会有这个数,这个记录一下每个数最后在哪出现即可。然后这样贪心的维护即可,因为前面具有决定性,因此能让前面小就让前面小,贪心是对的。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    vector<int> last(m + 1);
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        last[a[i]] = i;
    }

    vector<int> st(m + 1);
    stack<int> stk;
    for (int i = 1; i <= n; i ++) {
        if (!st[a[i]]) {
            while (stk.size() && stk.top() > a[i] && last[stk.top()] > i) {
                st[stk.top()] = false;
                stk.pop();    
            }
            stk.push(a[i]), st[a[i]] = true;
        }
    }

    while (stk.size() > m) stk.pop();
    vector<int> res;
    while (stk.size()) res.push_back(stk.top()), stk.pop();
    reverse(res.begin(), res.end());
    for (int i = 0; i < res.size(); i ++)
        cout << res[i] << " \n"[i == res.size() - 1];

    return 0;
}

拆拆

[Link](拆拆 - 题目 - Daimayuan Online Judge)

思路

  • 组合数学,分解质因数

​ 考虑到拼凑,一般会想到算数基本定理,我们可以将 x x x分解成一系列的质数次幂的乘积,要求本质不同的问题,问题转化为我们有很多组小球,要将每一组小球放到 y y y个箱子里且可以放空的方案数,且每组之间符合乘法原理。

​ 对于一组小球放到 y y y个箱子里不可以放空等价于, x 1 + x 2 + x 3 . . . + x y = s u m x_1+x_2+x_3...+x_y=sum x1+x2+x3...+xy=sum,相当于将总和分成 y y y份,根据隔板法方案为 C s u m − 1 y − 1 C_{sum-1}^{y-1} Csum1y1,如果可以防空,我们左右各加 y y y,等价于 x 1 + 1 + x 2 + 1 + x 3 + 1 + . . . + x y + 1 = s u m + y x_1+1+x_2+1+x_3+1+...+x_y+1=sum+y x1+1+x2+1+x3+1+...+xy+1=sum+y,这个时候就等价于 s u m + y sum+y sum+y个分 y y y份且不放空的情况,即 C s u m + y − 1 y − 1 C_{sum+y-1}^{y-1} Csum+y1y1

​ 对于正负的限制, y y y个数每个可正可负,一共 2 y 2^y 2y种方案,我们要的是正的,正负对半分因此再乘个 2 y − 1 2^{y-1} 2y1

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = N * 20, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int fact[M], infact[M];
int qmi(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = (LL) res * a % mod;
        a = (LL) a * a % mod;
        b >>= 1;
    }
    return res;
}
int C(int n, int m) {
    if (n < m) return 0;
    return (LL)fact[n] * infact[m] % mod * infact[n - m] % mod;
}
vector<PII> g(int x) {
    vector<PII> res;
    for (int i = 2; i <= x / i; i ++) 
        if (x % i == 0) {
            int cnt = 0;
            while (x % i == 0) x /= i, cnt ++;
            res.push_back({i, cnt});
        }
    
    if (x != 1) res.push_back({x, 1});
    return res;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    fact[0] = 1;
    for (int i = 1; i < M; i ++) fact[i] = (LL)fact[i - 1] * i % mod;
    infact[M - 1] = qmi(fact[M - 1], mod - 2);
    for (int i = M - 2; i >= 0; i --) infact[i] = (LL)infact[i + 1] * (i + 1) % mod;
    int T;    
    cin >> T;
    while (T -- ) {
        cin >> n >> m;
        vector<PII> ve = g(n);
        LL res = 1;
        for (auto t : ve)
            res = res * C(t.y + m - 1, m - 1) % mod;

        res = res * qmi(2, m - 1) % mod;
        cout << res << '\n';
    }
    return 0;
}

“Z”型矩阵

思路

  • 树状数组

​ 如果枚举左上角和长度怎么样至少都是 O ( n 3 ) O(n^3) O(n3)的。因此可以考虑枚举对角线,我们设 L i , j , R i , j L_{i,j},R_{i,j} Li,j,Ri,j分别 ( i , j ) (i,j) (i,j)向左向右连续的最长 z z z有多少。

​ 暴力枚举所有的对角线,由于题目限制,对于某个斜线的某一段是连续的 z z z才可能产生贡献,对于当前单独的一个斜线怎么计算贡献呢,首先将斜线上的点按 1 ∼ c n t 1\sim cnt 1cnt编号,对于第 i i i个点我们需要的是能于 R i R_i Ri产生贡献的,即 [ m a x ( 1 , i − R i + 1 ) , i ] [max(1,i-R_i+1),i] [max(1,iRi+1),i]这些点里有多少 L j L_j Lj是大于等于他们之间斜线距离的,由于斜线的长度固定,我们 L , R L,R L,R的长度也会固定,因此我们关注的就是查的这个斜线的某个区间有多少个合法的点,区间合法点可以用树状数组来搞,具体来说,对于一个点 i i i它有效的区间为 [ i , m i n ( c n t , i + L i − 1 ) ] [i,min(cnt,i+L_i-1)] [i,min(cnt,i+Li1)](再长斜线就会超过向左延申的长度了),因此我们可以记录一下每个到哪就不合法,动态的加入删除查询来统计贡献即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define int long long
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int n, m, k;
int a[N];
char g[3010][3010];
int L[3010][3010], R[3010][3010];
vector<PII> Line(3010);
int cnt, res;
int tr[N];
int lowbit(int x) {
    return x & -x;
}
void add(int x, int v) {
    for (; x <= cnt; x += lowbit(x)) tr[x] += v;
}
int sum(int x) {
    int res = 0;
    for (; x; x -= lowbit(x)) res += tr[x];
    return res;
}
int query(int l, int r) {
    if (l > r) return 0;
    return sum(r) - sum(l - 1);
}
void calc() {
    vector<int> G[cnt + 1];
    for (int i = 1; i <= cnt; i ++) {
        int l = Line[i].x, r = Line[i].y;        
        add(i, 1);
        res += query(max(1ll, i - r + 1), i);
        G[min(cnt, i + l - 1)].push_back(i);
        for (auto t : G[i]) add(t, -1);        
    }
    for (int i = 1; i <= cnt; i ++) tr[i] = 0;
    cnt = 0;
}
signed main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) 
        for (int j = 1; j <= m; j ++)
            cin >> g[i][j];

    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= m; j ++)
            if (g[i][j] == 'z')  L[i][j]  = L[i][j - 1] + 1;

    for (int i = 1; i <= n; i ++)
        for (int j = m; j; j --)
            if (g[i][j] == 'z') R[i][j] = R[i][j + 1] + 1;
    
    for (int k = 2; k <= n + m; k ++) {
        int x = max(1ll, k - m), y = k - x;
        
        while (true) {            
            if (g[x][y] == 'z') Line[++cnt] = {L[x][y], R[x][y]};
            else if (cnt) calc();
            x ++, y --;
            if (x > min(n, k - 1)) break;
        } 
        if (cnt) calc();
    }

    cout << res << '\n';
    return 0;
}

好序列

[Link](好序列 - 题目 - Daimayuan Online Judge)

思路

  • 启发式合并

​ 暴力来看怎么判断某个区间某个数是否唯一出现呢?
我们记录 p r e [ i ] pre[i] pre[i]表示 a i a_i ai上一次出现的位置, n x [ i ] nx[i] nx[i]表示 a i a_i ai下一次出现的位置,如果 p r e i < L & & n x i > R pre_i<L\&\& nx_i>R prei<L&&nxi>R则发现这个数是唯一出现的。

​ 如果某个数在某个区间 [ l , r ] [l,r] [l,r]里唯一出现,那么含这个数的 [ l , r ] [l,r] [l,r]的子区间一定合法,因此等价这个数将 [ l , r ] [l,r] [l,r]分成两半,设这个数位于 x x x,则只需判断 [ l , x − 1 ] , [ x + 1 , r ] [l,x-1],[x+1,r] [l,x1],[x+1,r]这两个子区间是否合法即可,这个可以递归来搞。

​ 这样复杂度还是 O ( n 2 ) O(n^2) O(n2),但是我们可以优化一下从两边往中间来枚举这个数是否唯一存在,这样就类似我们启发式合并里,将小的合并到大的这样每个数最多合并 l o g ( n ) log(n) log(n)次,这里我们们相当于每次都操作一个少的一边,是等价的,所以复杂度降下来了。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int pre[N], nx[N];
int a[N];
bool split(int l, int r) {
    if (l >= r) return true;
    int x = l, y = r;
    while (x <= y) {
        if (pre[x] < l && nx[x] > r) return split(l, x - 1) && split(x + 1, r);
        if (pre[y] < l && nx[y] > r) return split(l, y - 1) && split(y + 1, r);
        x ++, y --;
    }
    return false;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    int T;
    cin >> T;
    while (T -- ) {
        cin >> n;
        for (int i = 1; i <= n; i ++) cin >> a[i], pre[i] = 0, nx[i] = n + 1;
        map<int, int> mp;
        for (int i = 1; i <= n; i ++) {
            pre[i] = mp[a[i]];
            nx[mp[a[i]]] = i;
            mp[a[i]] = i;
        }
        if (split(1, n))  cout << "non-boring\n";
        else cout << "boring\n";
    }
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值