20230816HOJ训练

发布时间 2023-08-17 01:09:36作者: adam01

P1148 环上GCD

题意简述

\(n\) 个数 \(a_1,a_2,\cdots,a_n\)排成一个环,求出分割后每段的 \(\gcd\) 均大于1的方案数。

\(n\le 10^5\)

解题思路

看到这题,首先想到dp,但是发现如果断环成链会出现方案数被多次计算。

我们知道一定有段数包含 \(a_1\),我们暴力枚举从 \(a_n\rightarrow a_2\) 的前缀,若前缀为 \(a_x\rightarrow a_n\),那么从 \(a_1\) 开始暴力dp到 \(a_{x-1}\) 就行了。

但是这么做的时间复杂度仍然是 \(O(n^3)\), 我们发现从 \(a_n\)\(a_2\) 枚举前缀时 \(gcd(\{a_x,\cdots, a_n\})\) 单调不升,并且每次下降后的 \(gcd'\le \frac{gcd}{2}\),所以可以在 gcd 没变时使用上一次的dp值,变的时候再重新暴力dp,当 \(gcd=1\) 时退出,时间复杂度降为 \(O(n^2\log n)\)

我们发现,dp的转移式是 \(f_i = \sum\limits_{j < i}f_j[\gcd(a_{j+1},\cdots,a_i)>1]\),又有从 \(i\)\(j\), \(\gcd\) 单调不升,所以我们可以使用双指针,但是发现 \(\gcd\) 没有逆运算,所以只能暴力 \(st\) 表,因为每次dp时 \(j\) 指针只会移动最多 \(n\) 次,所以复杂度来到 \(O(n\log^2 n)\),可以通过。

细节

  1. 取模,负数 \(\rightarrow\) 正数
  2. dp时发现状态定义是在 \(a_i,a_{i+1}\) 间切断,所以要注意 \(f_0=f_1=1\)
  3. 注意每次dp时 \(a_1\) 相当于 \(\gcd(\{a_1,a_x,a_{x+1},\cdots,a_n\})\),所以st表求 \(\gcd\) 时要特判一下。

P1147 Assembly

这场比赛里最难的题。

题意简述

给你一个 \(R\times C\) 的网格,\(n\) 个点,坐标 \(R_i,C_i\),两个点 \(x,y\) 之间能直接通行当且仅当 \((R_x>R_y\land C_x>C_y)\lor(R_x<R_y\land C_x<C_y)\)

求出对于每个点,其他点到这个点经过的边数之和。

\(R,C\le 2.5\times 10^3,n\le 2.5\times 10^5\)

解题思路

img

对于上图,绿色区域代表可以和 \((4,5)\) 直接连接,那么如何处理红色区域中的点呢?

我们不妨找一下需要两步的点都在哪:

img

我们发现,要走两步的点就是绿色区域内的点可以直接到达的点,如果绿色区域内的还到达不了,就需要走三步,即黄色区域。

我们发现,黄色区域一定包含在红色区域中,所以我们想到dp。

\(f1_{i,j}\) 表示左下角区域为 \(i\le x\le R,1\le y\le j\)\(f2_{i,j}\) 表示右上角区域为 \(1\le x\le i,j\le y\le C\)

那么我们就有了状态,怎么设计转移方程呢?

我们发现,若有多个点均在 \(x\) 步能走到的范围(如上图),那么对于左下角的区域来说,一定是最左侧和最下面的点(橙色点)最优,因为选这两的点会尽可能的缩小 \(x+1\) 步能走到的范围,答案显然较非最左/下侧的蓝色点更优(棕、黄色区域分别表示选择蓝、橙色点的 \(x+1\) 步区域,黄色区域显然小于棕色区域,所以黄色区域更优)。

所以我们令 \(up,down,left,right\) 分别表示 \(x\) 步区域中最 上/下/左/右 侧的点,有 \(f1_{x,y}=f1_{down,left}+cnt_{down,left}^{\dagger}+sz_{x,y}^{\ddagger}-cnt_{x,y}\)

加减 \(cnt\) 是什么意思呢?因为 \(f1_{x,y}\) 表示 \((x,y)\) 对每个点要比1多走几步才能到达的和,需要减去 \((x,y)\)\(cnt\),所以从子状态转移时也要加上子状态的 \(cnt\)

同理,\(f2_{x,y}=f2_{up,right}+cnt_{up,right}+sz_{x,y}-cnt_{x,y}\)

\(\dagger\) : \(cnt_{x,y}\) 表示 \((x,y)\) 是否有点存在。

\(\ddagger\) : \(sz_{x,y}\) 表示 \((x,y)\) 左下/右上的点的总个数(定义视计算的是左下角还是右上角的区域而定)

细节

  1. 注意刚才着重讲的 \(cnt\) 加减
  2. 在处理 \(up,down,left,right\) 时如果有赋极大值,那么要记得和当前状态取 \(\min/\max\),否则不满足子状态比当前状态范围小的要求。

代码

P1148 环上GCD

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

#define int ll
const int N = 1e5 + 5, K = 20, p = 1e9 + 7;
int a[N], f[N], n, ans;
int st[N][K], hbit[N], ok = 0;

int getgcd(int l, int r, int g1)
{
    int len = hbit[r - max(l, 2ll) + 1];
    int g = __gcd(st[max(l, 2ll)][len], st[r - (1 << len) + 1][len]);
    if(l == 1) return __gcd(g, g1);
    else return g;
}

void calc(int x, int g)
{
    memset(f, 0, sizeof f);
    int w = 1;
    f[0] = f[1] = 1;
    for(int i = 1, j = 1; i < x; i ++)
    {
        while(j + 1 < x && getgcd(i, j + 1, g) > 1)
        {
            j ++;
            f[j] = (w + f[i - 1]) % p;
            w = (w + f[j]) % p;
        }
        w = (w - f[i]) % p;
    }
    ans = (ans + f[x - 1] - ok) % p;
}

void init()
{
    int lim = 1, p = -1;
    for(int i = 1; i <= n; i ++)
    {
        if(i == lim) lim <<= 1, p ++;
        hbit[i] = p;
    }
    for(int i = 1; i <= n; i ++)
        st[i][0] = a[i];
    for(int j = 1; j < K ; j ++)
    for(int i = 1; i <= n; i ++)
        st[i][j] = __gcd(st[i][j - 1], st[min(n, i + (1 << j - 1))][j - 1]);
}

signed main()
{
    #define fio(x) freopen(#x".in", "r", stdin);freopen(#x".out", "w", stdout)
    fio(gcd);

    ios::sync_with_stdio(0);cin.tie(0);
    
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    
    init();
    ok = st[1][K - 1] > 1;

    int g = a[1], lstg = g;
    calc(n + 1, g);

    for(int i = n; i > 1; i --)
    {
        g = __gcd(g, a[i]);
        if(g == 1) break;
        if(g != lstg) calc(i, g);
        else ans = (ans + f[i - 1] - ok) % p;
        lstg = g;
    }
    cout << ((ans + ok) % p + p) % p;

    return 0;
}

P1147 Assembly

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2505, M = 250005;
int f1[N][N], f2[N][N], n, x[M], y[M];
int cnt[N][N], s[N][N];
int u[N][N], d[N][N], l[N][N], r[N][N];

//fastio here

signed main()
{
    #define fio(x) freopen(#x".in", "r", stdin);freopen(#x".out", "w", stdout)
    fio(assembly);
    ios::sync_with_stdio(0);cin.tie(0);

    int V = 0;
    read(n);
    for(int i = 1; i <= n; i ++)
        read(x[i], y[i]), cnt[x[i]][y[i]] ++, V = max(V, max(x[i], y[i]));
    
    for(int i = 0; i <= V + 1; i ++)
    for(int j = 0; j <= V + 1; j ++)
    {
        if(cnt[i][j]) u[i][j] = i, l[i][j] = j;
        else u[i][j] = V + 1, l[i][j] = V + 1;
        if(i) u[i][j] = min(u[i][j], u[i - 1][j]), l[i][j] = min(l[i][j], l[i - 1][j]);
        if(j) l[i][j] = min(l[i][j], l[i][j - 1]), u[i][j] = min(u[i][j], u[i][j - 1]);
    }

    for(int i = V + 1; i >= 0; i --)
    for(int j = V + 1; j >= 0; j --)
    {
        if(cnt[i][j]) d[i][j] = i, r[i][j] = j;
        else d[i][j] = 0, r[i][j] = 0;
        if(i <= V) d[i][j] = max(d[i][j], d[i + 1][j]), r[i][j] = max(r[i][j], r[i + 1][j]);
        if(j <= V) r[i][j] = max(r[i][j], r[i][j + 1]), d[i][j] = max(d[i][j], d[i][j + 1]);
    }

    for(int i = 1; i <= V; i ++)
    for(int j = V; j >= 1; j --)
    {
        s[i][j] = s[i - 1][j] + s[i][j + 1] - s[i - 1][j + 1] + cnt[i][j];
        int i0 = min(i, u[i - 1][j - 1]), j0 = max(j, r[i + 1][j + 1]);
        f1[i][j] = s[i][j] - cnt[i][j] + f1[i0][j0] + cnt[i0][j0];
    }

    memset(s, 0, sizeof s);
    for(int i = V; i >= 1; i --)
    for(int j = 1; j <= V; j ++)
    {
        s[i][j] = s[i + 1][j] + s[i][j - 1] - s[i + 1][j - 1] + cnt[i][j];
        int i0 = max(i, d[i + 1][j + 1]), j0 = min(j, l[i - 1][j - 1]);
        f2[i][j] = s[i][j] - cnt[i][j] + f2[i0][j0] + cnt[i0][j0];
    }

    for(int i = 1; i <= n; i ++)
        cout << n - 1 + f1[x[i]][y[i]] + f2[x[i]][y[i]] << "\n";

    return 0;
}