大一暑假第一周训练

考试差不多考完了,过几天还有一场英语

停止训练挺久了,现在暑假开始,可以全身心投入acm了

这一周从周四开始,猛练一波

现在欠了一堆题目没补,先补题吧

周四

An Easy Problem(思维+堆)

这道题有我的思路是对于n * m

(n - 1) * m 和 n * (m - 1) 显然是更小的

所以可以用堆来维护,这一对取出之后就压入比其更小的两个

注意要去重。同时结构体的重载注意一些细节

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
struct node
{
    ll x, y;
    bool operator < (const node& rhs) const
    {
        if(x * y == rhs.x * rhs.y) return x < rhs.x; //细节 不然(2, 3) 和(3, 2) 会看作相同
        return x * y < rhs.x * rhs.y;
    }
};

int main()
{
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);

    priority_queue<node> q;
    q.push(node{n, m});
    set<node> s;

    while(1)
    {
        node u = q.top(); q.pop();
        if(--k == 0)
        {
            printf("%lld\n", u.x * u.y);
            break;
        }

        if(s.find(node{u.x - 1, u.y}) == s.end())
        {
            q.push(node{u.x - 1, u.y});
            s.insert(node{u.x - 1, u.y});
        }
        if(s.find(node{u.x, u.y - 1}) == s.end())
        {
            q.push(node{u.x, u.y - 1});
            s.insert(node{u.x, u.y - 1});
        }
    }

    return 0;
}

 写完看了AC代码,发现有写得非常简洁的

这下面这个和我上面的思路是一样的

但是其一开始就把m 2m 3m全部加入堆中

相当于把横坐标都确定了。然后每次新加入的都是改变纵坐标

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;

int main()
{
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);

    priority_queue<pair<ll, ll>> q;
    _for(i, 1, n) q.push(make_pair(i * m, i));

    while(--k)
    {
        pair<ll, ll> u = q.top(); q.pop();
        q.push(make_pair(u.first - u.second, u.second));
    }
    printf("%lld\n", q.top().first);

    return 0;
}

周五

An Easy Problem(二分答案)

还有一个思路是二分答案,这倒挺骚的

因为如果固定了面积,很容易算出小于等于这个面积的有多少种可能

知道了小于等于这个面积的个数,那么用总数减去这个,就是大于当前面积的个数

如果第k大,那么大于它的就有k-1个

写的时候有个细节

拿样例来说,1 2 2 3 3 4 6 6 9

这时4 和 5 都是有3个大于它的数,但是显然4是答案而5不是

这个时候这么处理

其实就是取等号的时候要往左移,而取等号是满足条件的

那二分答案

那就设为0 0 0 1 1 1 1的形式

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
int n, m, k;

bool check(ll key)
{
    ll cnt = 0;
    _for(i, 1, n)
        cnt += min(key / i, 1LL * m);
    return 1LL * n * m - cnt <= k - 1;
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);

    ll l = 0, r = 1e13;
    while(l + 1 < r)
    {
        ll m = l + r >> 1;
        if(check(m)) r = m;
        else l = m;
    }
    printf("%lld\n", r);

    return 0;
}

Double(时间复杂度)

这题很容易想到一个暴力的做法,就是枚举每一个点看它能不能留到最后

但是当时觉得这样暴力显然会超时

比赛的时候队友告诉我一直乘2很快就会乘到很大的数,我才醒悟过来

一直乘2乘30次左右就最大了,所以可以很快就结束

因此每次如果乘到比最大数还大的时候就结束了,并不需要遍历完整个区间

甜甜圈(树状数组)

当时我想这道题的时候卡住了,模拟肯定超时。是队友过的

这题可以发现甜甜圈的相对位置是不变的,可以看作转化成一个区间

那么每次就求区间和就行了,注意去掉的区间不能算

所以就是单点修改,区间求和

树状数组就行了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 2e5 + 10;
int f[N];

int lowbit(int x) { return x & (-x); }

void add(int x, int p)
{
    for(; x < N; x += lowbit(x)) f[x] += p;
}

int s(int x)
{
    int res = 0;
    for(; x; x -= lowbit(x))
        res += f[x];
    return res;
}

int sum(int l, int r)
{
    if(r < l) return 0;
    return s(r) - s(l - 1);
}

int main()
{
    int n1, n2;
    scanf("%d%d", &n1, &n2);

    int mx = 0, t;
    vector<pair<int, int>> ve;
    _for(i, 1, n1)
    {
        int x; scanf("%d", &x);
        ve.push_back(make_pair(x, n1 - i + 1));
        if(mx < x) { mx = x; t = i - 1; }
    }
    _for(i, 1, n2)
    {
        int x; scanf("%d", &x);
        ve.push_back(make_pair(x, n1 + i));
        if(mx < x) { mx = x; t = i - 1; }
    }
    sort(ve.begin(), ve.end(), greater<pair<int, int>>());

    ll ans = t;
    _for(i, 1, n1 + n2) add(i, 1);
    rep(i, 0, ve.size() - 1)
    {
        int p1 = ve[i].second, p2 = ve[i + 1].second;
        ans += sum(min(p1, p2) + 1, max(p1, p2) - 1);
        add(p1, -1);
    }
    printf("%lld\n", ans);

    return 0;
}

Don't Really Like How The Story Ends (dfs + 栈)

这道题依然是队友做的

这道题要理解dfs的栈的过程

https://blog.csdn.net/weixin_45717583/article/details/117512217

这哥们写的很好

分几种情况

如果v直接与v+1相连就直接访问

如果v存在未访问的点,且不是v+1,这个时候必须连一条边

如果v的点全部都是访问过的,说明到底了,这个时候可以退栈,退到存在有未访问过的点的节点,然后就转化为前面两条

全部遍历过一遍之后,如果还有未访问过的点,说明不连通,这时就直接从第1个点连一条边到其他连通分量

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
vector<int> g[N];
int n, m, ans, now;

void dfs(int u)
{
    now++;                   //每次now++到下一个点
    rep(i, 0, g[u].size())
    {
        int v = g[u][i];
        if(v < now) continue;     //肯定是访问过的点
        else if(v == now) dfs(v);
        else
        {
            ans++;
            dfs(now);
            i--;          //这个点相当于没访问
        }
    }
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d", &n, &m);
        _for(i, 1, n) g[i].clear();
        while(m--)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u].push_back(v);
            g[v].push_back(u);
        } 
        _for(i, 1, n) sort(g[i].begin(), g[i].end()); //边排序

        now = 1;
        ans = 0;
        dfs(1);
        while(now <= n) //最后还剩下点没遍历,说明不连通
        {
            dfs(now);
            ans++;
        }

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

    return 0;
}

二维树状数组模板

比赛时遇到了树状数组套树状数组,练一练

#include <bits/stdc++.h>
#define rep(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 1e3;
int f[MAXN][MAXN], n, m, q;

int lowbit(int x) { return x & (-x); }

void add(int x, int y, int p)
{
    for(; x <= n; x += lowbit(x))
        for(int i = y; i <= m; i += lowbit(i))
            f[x][i] += p;
}

int s(int x, int y)
{
    int res = 0;
    for(; x; x -= lowbit(x))
        for(int i = y; i; i -= lowbit(i))
            res += f[x][i];
    return res;
}

int sum(int x1, int y1, int x2, int y2)
{
    return s(x2, y2) - s(x2, y1 - 1) - s(x1 - 1, y2) + s(x1 - 1, y1 - 1);
}

int main()
{
	scanf("%d%d%d", &n, &m, &q);
	_for(i, 1, n)
		_for(j, 1, m)
		{
			int x; scanf("%d", &x);
			add(i, j, x);
		}

	while(q--)
	{
		int op, x1, y1, x2, y2, p;
		scanf("%d", &op);
		if(op == 1) scanf("%d%d%d", &x1, &y1, &p), add(x1, y1, p);
		else
		{
			scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
			printf("%d\n", sum(x1, y1, x2, y2));
		}
	}
	return 0;
}

P4054 [JSOI2009]计数问题(二维树状数组)

二维树状数组练一下

这道题c很小只有100,直接暴力开100个二维树状数组就行了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 310;
const int M = 110;
int f[M][N][N], a[N][N], n, m, q;

int lowbit(int x) { return x & -x; }

void add(int c, int x, int y, int p)
{
    for(int i = x; i <= n; i += lowbit(i))
        for(int j = y; j <= m; j += lowbit(j))
            f[c][i][j] += p;
}

int s(int c, int x, int y)
{
    int res = 0;
    for(int i = x; i; i -= lowbit(i))
        for(int j = y; j; j -= lowbit(j))
            res += f[c][i][j];
    return res;
}

int sum(int c, int x1, int y1, int x2, int y2)
{
    return s(c, x2, y2) + s(c, x1 - 1, y1 - 1) - s(c, x2, y1 - 1) - s(c, x1 - 1, y2);
}

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, n)
        _for(j, 1, m)
        {
            scanf("%d", &a[i][j]);
            add(a[i][j], i, j, 1);
        }

    scanf("%d", &q);
    while(q--)
    {
        int op; scanf("%d", &op);
        if(op == 1)
        {
            int x, y, c;
            scanf("%d%d%d", &x, &y, &c);
            add(a[x][y], x, y, -1);
            add(c, x, y, 1);
            a[x][y] = c;
        }
        else
        {
            int x1, y1, x2, y2, c;
            scanf("%d%d%d%d%d", &x1, &x2, &y1, &y2, &c);
            printf("%d\n", sum(c, x1, y1, x2, y2));
        }
    }

	return 0;
}

poj 2155(二维差分 + 二维树状数组)

首先要发现一个性质,0操作了奇数次是1,偶数次是0

所以其实问题就转化为,每次让一个矩阵加1,然后每次询问一个点加了多少次

二维树状数组可以解决二维上的单点修改,每次查询一个矩阵的和

用二分维差分,就可以转化为每次一个矩阵的每个数加上一个值,每次询问一个点的值是多少

二维差分就模仿一维差分的写法就好

一维差分是d[i] = a[i] - a[i - 1]   

求值是前缀和a[i] = d[1]~d[i]

二维差分就是 d[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1]   

求值是前缀和 a[i][j] = d[1][1] ~ d[i][j]

一维差分每次修改会改两个地方

二维差分每次修改会改四个地方,画个图按照d[i][j]定义就知道了

#include <cstdio>
#include <cstring>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int f[N][N], n, q;

int lowbit(int x) { return x & -x; }

void add(int x, int y, int p)
{
    for(int i = x; i <= n; i += lowbit(i))
        for(int j = y; j <= n; j += lowbit(j))
            f[i][j] += p;
}

int sum(int x, int y)
{
    int res = 0;
    for(int i = x; i; i -= lowbit(i))
        for(int j = y; j; j -= lowbit(j))
            res += f[i][j];
    return res;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        memset(f, 0, sizeof f);
        scanf("%d%d", &n, &q);
        while(q--)
        {
            char op[5];
            scanf("%s", op);
            if(op[0] == 'C')
            {
                int x1, y1, x2, y2;
                scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
                add(x1, y1, 1);
                add(x2 + 1, y2 + 1, 1);
                add(x1, y2 + 1, -1);
                add(x2 + 1, y1, -1);
            }
            else
            {
                int x, y;
                scanf("%d%d", &x, &y);
                printf("%d\n", sum(x, y) % 2);
            }
        }
        puts("");
    }

	return 0;
}

Kera's line segment(二维树状数组 + 转化为二维平面)

这道题就是省赛遇到的题

这道题的关键在于L <= l   r <= R

这个区间完全包含的等式,转化到二维平面上

区间(l, r)转化为点(l, r)

这样每次就是询问右下区域的点

那么可以用二维树状数组。

首先可以翻转一下转化为左下区间的点

二维树状数组原来是不能求区间最值的 因为不像求和那样可以直接s(r) - s(l - 1)

除非每次的最值都是前缀最值,即(1, r)的最值

这道题刚好满足,每次求的最值都是(1, 1) 到(x,y)的最值

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 3000 + 10;
int f1[N][N], f2[N][N], n, q;

int lowbit(int x) { return x & -x; }

void add(int x, int y, int p)
{
    for(int i = x; i <= 3000; i += lowbit(i))
        for(int j = y; j <= 3000; j += lowbit(j))
        {
            f1[i][j] = max(f1[i][j], p);
            f2[i][j] = min(f2[i][j], p);
        }
}

int query(int x, int y)
{
    int mx = 0, mi = 1e9;
    for(int i = x; i; i -= lowbit(i))
        for(int j = y; j; j -= lowbit(j))
        {
            mx = max(mx, f1[i][j]);
            mi = min(mi, f2[i][j]);
        }
    return mx - mi;
}

int main()
{
    memset(f2, 0x3f, sizeof f2);
    scanf("%d%d", &n, &q);
    _for(i, 1, n)
    {
        int l, r, v;
        scanf("%d%d%d", &l, &r, &v);
        l = 3000 - l + 1;
        add(l, r, v);
    }

    int last = 0;
    while(q--)
    {
        int op, l, r, v;
        scanf("%d%d%d", &op, &l, &r);
        l ^= last; r ^= last;
        l = 3000 - l + 1;
        if(op == 1) scanf("%d", &v), add(l, r, v);
        else printf("%d\n", last = query(l, r));
    }

	return 0;
}

Byfibonacci(01背包拓展)

比赛的时候我想到了dp

也就是dp[i] = dp[i - k] * k

但是这样有两个问题,一个是会超时,一个是会重复选一个数

然后就卡住了

赛后看题解,dp是正解之一

怎么解决上面这两个问题呢

一个数只能选一次的话,用到了01背包的思路

把数看作容量

价值的更更新方式就是上面那个dp方程

模仿01背包,容量逆序,这样就可以保证一个数只选了一次

然后这个01背包是要刚好填满的,也就是说有很多不合法的状态

因为转移的时候是乘法,所以不合法为0就可以了

因此也有了一个优化,就是第二维一开始从前几个容量的和开始,而不是从1e7,也就是总容量开始。这就解决了超时的问题

这道题是一个数拆成几个数,利用了01背包的思路

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e7 + 10;
const int mod = 998244353;
int dp[N], f[40], s[40];

int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }

int main()
{
    f[0] = f[1] = 1; s[0] = 1; s[1] = 2;
    _for(i, 2, 40)
    {
        f[i] = add(f[i - 1], f[i - 2]);
        s[i] = add(s[i - 1], f[i]);
    }

    dp[0] = 1;
    _for(j, 0, 34)
        for(int i = min((int)1e7, s[j]); i >= f[j]; i--)
            dp[i] = add(dp[i], mul(dp[i - f[j]], f[j]));

    int T; scanf("%d", &T);
    while(T--)
    {
        int x; scanf("%d", &x);
        printf("%d\n", dp[x]);
    }

	return 0;
}

周六

History(分层图最短路)

感觉比赛时我认真思考是可以做出来的。这题在比赛时主要是队友在想,我连题意都没有太弄清楚

这个边长度的变换,猜它有循环。打表发现每4个就是一个循环

所以就分层图最短路就好了,分4层

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 2e5 + 10;
struct node
{
    int v; ll w;
    bool operator < (const node& rhs) const
    {
        return w > rhs.w;
    }
};
vector<node> g[N << 2];
int n, m, p;
ll d[N << 2];

int add(int a, int b) { return (a + b) % p; }
int mul(int a, int b) { return 1LL * a * b % p; }

int binpow(int a, int b)
{
    int res = 1;
    for(; b; b >>= 1)
    {
        if(b & 1) res = mul(res, a);
        a = mul(a, a);
    }
    return res;
}

int inv(int x)
{
    if (x < 0) x += p;
    return binpow(x, p - 2);
}

ll solve()
{
    priority_queue<node> q;
    _for(i, 1, 4 * n) d[i] = 1e18;
    d[1] = 0;
    q.push(node{1, d[1]});

    while(!q.empty())
    {
        node x = q.top(); q.pop();
        int u = x.v;
        if(x.w != d[u]) continue;
        for(auto t: g[u])
        {
            int v = t.v; ll w = t.w;
            if(d[v] > d[u] + w)
            {
                d[v] = d[u] + w;
                q.push(node{v, d[v]});
            }
        }
    }

    ll ans = 1e18;
    _for(i, 1, 4)
        ans = min(ans, d[i * n]);
    return ans;
}

int main()
{
    scanf("%d%d%d", &n, &m, &p);
    while(m--)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        _for(i, 0, 3)
        {
            g[i * n + u].push_back(node{((i + 1) % 4) * n + v, w});
            w = mul(1 + w, inv(1 - w));
        }
    }
    printf("%lld\n", solve());

	return 0;
}

Birthday Cake(双哈希)

一直没学哈希,今天搞一搞哈希

这道题是比赛的一道题

思路其实蛮简单的,但是我把string作为map的键值,超时了

因为其实string比较的时候要一个一个比,就很慢

那么这个时候就可以用哈希,把string转化成整数,整数比较就很快

处理过程类似131进制数

哈希有三种方法,自然溢出,单哈希,双哈希

自然溢出就是利用ull的自动取模,单哈希就是模一个质数如1e9 + 7

双哈希就是模两个质数如1e9 + 7 1e9 + 9

自然溢出和单哈希都可能被出题人卡,但是双哈希就很安全

这道题一开始用自然溢出写

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef unsigned long long ull;
typedef long long ll;
const int N = 4e5 + 10;
const int base = 131;
map<ull, int> mp;
ull Hash[N], p[N];
string s[N];
int n;

ull get_hash(string s)
{
    ull res = 0;
    int len = s.size();
    rep(i, 0, len)
        res = res * base + s[i];
    return res;
}

ull cut(int l, int r)   //计算某一个子串的哈希值,记住公式
{
    if(l - 1 < 0) return Hash[r];                  //注意负数的情况
    return Hash[r] - Hash[l - 1] * p[r - l + 1];
}

int main()
{
    p[0] = 1;
    _for(i, 1, 4e5) p[i] = p[i - 1] * base; //初始化p数组 表示base的p次方 cut用到

    ll ans = 0;
    scanf("%d", &n);
    _for(i, 1, n)
    {
        cin >> s[i];
        ll t = get_hash(s[i]);
        if(mp[t]) ans += mp[t];
        mp[t]++;
    }

    _for(i, 1, n)
    {
        string t = s[i];
        int len = t.size();
        Hash[0] = t[0];
        rep(i, 1, len) Hash[i] = Hash[i - 1] * base + t[i];

        _for(k, 1, len / 2)
            if(cut(0, k - 1) == cut(len - k, len - 1))
                ans += mp[cut(k, len - k - 1)];
    }
    printf("%lld\n", ans);

	return 0;
}

过了97%的点,被卡了

之后用双哈希写,过了。

双哈希就是两种模数,得出的两个答案用pair存

哈希还可以用来O(1)时间判断两个子串是否相等

O(n)预处理

#include <bits/stdc++.h>
#define pa pair<ull, ull>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef unsigned long long ull;
typedef long long ll;
const int N = 4e5 + 10;
const int base = 131;
const int mod1 = 1e9 + 7;
const int mod2 = 998244353;

map<pa, int> mp;
ull Hash[N][2], p[N][2];
string s[N];
int n;

pa get_hash(string s)
{
    ull res1 = 0, res2 = 0;
    int len = s.size();
    rep(i, 0, len)
    {
        res1 = (res1 * base % mod1 + s[i]) % mod1;
        res2 = (res2 * base % mod2 + s[i]) % mod2;
    }
    return make_pair(res1, res2);
}

pa cut(int l, int r)   //计算某一个子串的哈希值,记住公式
{
    if(l - 1 < 0) return make_pair(Hash[r][0], Hash[r][1]);
    ull res1 = (Hash[r][0] - Hash[l - 1][0] * p[r - l + 1][0] % mod1 + mod1) % mod1;
    ull res2 = (Hash[r][1] - Hash[l - 1][1] * p[r - l + 1][1] % mod2 + mod2) % mod2;
    return make_pair(res1, res2);
}

int main()
{
    p[0][0] = p[0][1] = 1;
    _for(i, 1, 4e5)
    {
        p[i][0] = p[i - 1][0] * base % mod1;
        p[i][1] = p[i - 1][1] * base % mod2;
    }

    ll ans = 0;
    scanf("%d", &n);
    _for(i, 1, n)
    {
        cin >> s[i];
        pa t = get_hash(s[i]);
        if(mp[t]) ans += mp[t];
        mp[t]++;
    }

    _for(i, 1, n)
    {
        string t = s[i];
        int len = t.size();
        Hash[0][0] = Hash[0][1] = t[0];
        rep(i, 1, len)
        {
            Hash[i][0] = (Hash[i - 1][0] * base % mod1 + t[i]) % mod1;
            Hash[i][1] = (Hash[i - 1][1] * base % mod2 + t[i]) % mod2;
        }
        _for(k, 1, len / 2)
            if(cut(0, k - 1) == cut(len - k, len - 1))
                ans += mp[cut(k, len - k - 1)];
    }
    printf("%lld\n", ans);

	return 0;
}

白兔的字符串(hash)

找几道哈希的题做一做

这道题首先要解决一个循环的问题

倍长取子串就好了

然后把值都加入一个unordered_map中

开始加入set中T了,map也T

然后就暴力枚举每个串的子串就行了

用自然溢出,写起来方便

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef unsigned long long ull;
const int base = 131;
const int N = 2e6 + 10;

ull Hash[N], p[N];
unordered_map<ull, bool> vis;
string s;
int n;

ull cut(int l, int r)
{
    if(l - 1 < 0) return Hash[r];
    return Hash[r] - Hash[l - 1] * p[r - l + 1];
}

int main()
{
    p[0] = 1;
    rep(i, 1, N) p[i] = p[i - 1] * base;

    cin >> s;
    int len = s.size();
    s = s + s;

    Hash[0] = s[0];
    rep(i, 1, len * 2) Hash[i] = Hash[i - 1] * base + s[i];
    rep(i, 0, len) vis[cut(i, i + len - 1)] = 1;

    cin >> n;
    while(n--)
    {
        cin >> s;
        int t = s.size(), ans = 0;

        Hash[0] = s[0];
        rep(i, 1, t) Hash[i] = Hash[i - 1] * base + s[i];
        rep(i, 0, t)
        {
            if(i + len - 1 >= t) break;
            ans += vis[cut(i, i + len - 1)];
        }
        cout << ans << endl;
    }

	return 0;
}

K串 (莫队 + 哈希 + 前缀和)

这题非常精彩,涉及到了挺多知识点

莫队和哈希我都会,但是我没有想到用前缀和的思想,导致没做出来

区间[l, r]中每个字母的个数是k的倍数

那么首先化为前缀和,处理出前缀和后就是

[1, r] - [1, l-1]中每个字母的个数是k的倍数

这样还不够,注意差是k的倍数,意味着mod k是同余的

所以如果我们把每个字母的个数都模k

那么就转化为[1, r] 和[1, l-1]是相等的
也就是26元组 a[l - 1] 和 a[r] 是相等的

那么就转化为每次询问区间[l, r]中有多少对相同的26元组

这不就是莫队模板题吗,求一个区间内相同数的个数

当然,这里的26元组要哈希一下扔到unorderer_map中

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef unsigned long long ull;
const int N = 3e4 + 10;
const int base = 131;

unordered_map<ull, int> mp;
int cnt[N][30], ans[N], sum, m, k;
ull Hash[N];
string s;
struct query
{
    int l, r, bl, id;
}q[N];

bool cmp(query a, query b)
{
    if(a.bl != b.bl) return a.bl < b.bl; //分块
    if(a.bl & 1) return a.r < b.r;       //同一块内按照右端点
    return a.r > b.r;                    //奇偶块优化
}

void add(int x) { sum += mp[Hash[x]]; mp[Hash[x]]++; }
void del(int x) { mp[Hash[x]]--; sum -= mp[Hash[x]]; }

int main()
{
    cin >> k >> s;
    int len = s.size();
    rep(i, 0, len)
    {
        if(i > 0) rep(j, 0, 26) cnt[i][j] = cnt[i - 1][j];
        cnt[i][s[i] - 'a'] = (cnt[i][s[i] - 'a'] + 1) % k;

        ull t = 0;
        rep(j, 0, 26) t = t * base + cnt[i][j];
        Hash[i + 1] = t;    //i + 1和题目标号一致
    }

    cin >> m;
    int block = sqrt(len);
    _for(i, 1, m)
    {
        int l, r;
        cin >> l >> r;
        q[i] = query{l, r, l / block, i};
    }
    sort(q + 1, q + m + 1, cmp);

    int l = 1, r = 0;
    _for(i, 1, m)
    {
        int ll = q[i].l - 1, rr = q[i].r; // -1关键 因为这里WA了好久
        while(l < ll) del(l++);          //del先删掉再移动
        while(l > ll) add(--l);          //add反过来,先移动再操作
        while(r < rr) add(++r);          //l一个add一个del r同样
        while(r > rr) del(r--);
        ans[q[i].id] = sum;
    }
    _for(i, 1, m) cout << ans[i] << endl;

	return 0;
}

Fake Math Problem(组合数学)

那个式子大概就是

每次给一个ai

1

n

n * (n - 1)

n……(n - ai + 1)

上面求和

然后对于每个ai得到的数再求和

计算这个东西

关键在于发现这个模数不是个质数,当时我们队就被坑了

既然这样,相乘的过程只要含有了这个模数的所有因子就为0了

所以暴力就好了,如果为0就直接break

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int mod = 998241383;

int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        int n, ans = 0;
        scanf("%d", &n);
        _for(i, 0, n)
        {
            ans = add(ans, 1);
            int k, t = 1, cnt = 0;
            scanf("%d", &k);
            for(int j = i; j >= i - k + 1 && t; j--)
            {
                t = mul(t, j);
                ans = add(ans, t);
                cnt = add(cnt, t);
            }
        }
        printf("%d\n", ans);
    }

	return 0;
}

周日

今天看了科比的演讲,太励志了

感觉我的努力程度远远不及科比。

他的那种上进,自律,专注真的令我非常震撼

暑假是个很好的时间,努力提升自己

Good Game, GG(博弈论)

这题就分类讨论

x = 1 Alice多一次

x = 2 如果Bob操作1次,Alice可以操作两次,还不如不操作

对于奇数x > 1 因为2 Bob不可能去操作,所以分割成2 + 一个奇数

这样可以多操作x / 2 + 1次

对于偶数x > 2 肯定是分成两个偶数

这样可以多操作x / 2 - 1次

比赛的时候我纠结在具体拆的过程中了,搞得很乱

其实和拆的顺序没有关系,要宏观的观察

还有要开long long 好坑

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        int n;
        long long a = 0, b = 0;
        scanf("%d", &n);

        _for(i, 1, n)
        {
            int x; scanf("%d", &x);
            if(x == 1) a++;
            else if(x % 2 == 1) a += x / 2 + 1;
            else b += x / 2 - 1;
        }
        puts(a > b ? "Alice" : "Bob");
    }

	return 0;
}

手动计算(计算几何)

比赛时队友手算积分过的

官方的题解很骚

题目给的精度要求很小,只要求输出1位小数

所以可以直接切成很多个以0.01为边长的正方形

然后看每个正方形在不在椭圆内部,在的话加上这个正方形的面积

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        double a, b, c, d;
        scanf("%lf%lf%lf%lf", &a, &b, &c, &d);

        double ans = 0;
        for(double x = -8; x <= 8; x += 0.01)
            for(double y = -8; y <= 8; y += 0.01)
                if(x * x / (a * a) + y * y / (b * b) <= 1 || x * x / (c * c) + y * y / (d * d) <= 1)
                    ans += 0.01 * 0.01;
        printf("%.1f\n", ans);
    }

	return 0;
}

Dance with a stick(计算几何)

这道题我当时猜了一个结论,就是一条直接左右两边点相同就可以了

当时具体实现想的有点复杂,队友也在写其他题,就没写了

最后证明这个结论是对的。同时实现可以写的很简单

直接点排序,排成一条类似y = x的斜线

取中间点,方向是-1 1e9就可以了

这样就非常巧妙

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int main()
{
    int n;
    vector<pair<int, int>> ve;

    scanf("%d", &n);
    _for(i, 1, n)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        ve.push_back(make_pair(x, y));
    }
    sort(ve.begin(), ve.end());

    if(n % 2 == 0)
    {
        puts("No");
        return 0;
    }
    puts("Yes");
    printf("%d %d -1 1000000000\n", ve[n / 2].first, ve[n / 2].second);

	return 0;
}

Honeycomb(思维)

这道题关键是点的坐标不好确立

可以从里到外一圈一圈来看,判断当前点在第几圈

如何判断呢,我们可以发现每一圈最大的点数是3 *k*(k-1)

于是可以二分,用vector实现,用lower_bound

然后定位之后,再沿着那一圈绕,找到当前点的坐标。

有了点的坐标就可以算出x和y方向上的差值

之后就很简单了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int dir[6][2] = {1, 1, -1, 1, -2, 0, -1, -1, 1, -1, 2, 0};
struct node { int x, y; };
vector<int> ve;

node get(int now)
{
    int p = lower_bound(ve.begin(), ve.end(), now) - ve.begin();
    int x = p, y = -p, t = 3 * p * (p + 1) + 1 - now;
    rep(i, 0, 6)
    {
        int k = min(t, p); t -= k;
        x += dir[i][0] * k;
        y += dir[i][1] * k;
        if(!t) break;
    }
    return node{x, y};
}

int main()
{
    for(int i = 1; 3 * i * (i - 1) + 1 <= 1e9; i++)
        ve.push_back(3 * i * (i - 1) + 1);

    int T; scanf("%d", &T);
    while(T--)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        node A = get(a);
        node B = get(b);

        int dx = abs(A.x - B.x), dy = abs(A.y - B.y);
        if(dy > dx) printf("%d\n", dy + 1);
        else printf("%d\n", dy + (dx - dy) / 2 + 1);
    }

	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值