ARC 乱做

ARC125D Unique Subsequence

\(f_i\) 表示以 \(i\) 为结尾的合法子序列个数,同时记 \(pre_i\)\(i\) 前面第一个与 \(a_i\) 相等的数的位置。

\[f_i=\sum_{j=pre_i}^{i-1}f_j \]

转移完把 \(f_{pre_i}\) 清零,树状数组维护即可。

ARC125E Snack

容易建出一个网络流模型,最大流等于最小割,试着求最小割。

由于每个零食向第 \(i\) 个小孩连边的流量都是 \(b_i\),所以零食对每个小孩没有区别,那么按 \(a_i\) 从小到大排序,枚举左边割前 \(i\) 条边,右边则是 \(\min((n-i)b_i,c_i)\)

于是将小孩按照 \(\dfrac{c_i}{b_i}\) 排序,然后维护一个指针。

ARC125F Tree Degree Subset Sum

先将每个点的度数减一,可以证明答案不变。

\(mn_i\) 为度数和为 \(i\) 的最小点数,\(mx_i\) 为最大点数,\(z\) 为度数为 \(0\) 的点数,易得:

\(i-mn_i\ge-z\)\(i-mx_i\le z-2\)

由于 \(mn_i\) 中没有度数为 \(0\) 的点,\(mx_i\) 中包含了所有度数为 \(0\) 的点,所以 \([mn_i,mn_i+z]\cup [mx_i-z,mx_i]\) 都合法,显然为 \([mn_i,mx_i]\)

计算 \(mn_i,mx_i\) 可以背包,不难发现不同的度数有 \(\sqrt n\) 种,多重背包即可。

ARC127D Sum of Min of Xor

\(c_i=a_i\oplus b_i\),可以发现若 \(c_i\)\(c_j\) 的第 \(k\) 位不同,那么 \(a_i\oplus a_j\)\(b_i\oplus b_j\) 的第 \(k\) 位也不同,那么从高到低判断每一位,当前集合中 \(c\) 的高位都相同,然后将当前位为 \(0/1\) 的分开,统计一下两个集合间的贡献,然后分别递归处理。

当递归到最低位后,说明两数相等,那么再简单统计一下贡献即可。

Code
ll solve(int l, int r, int k)
{
    ll res = 0;
    if(l > r) return 0;
    if(k < 0)
    {
        int s[2];
        for(int j = 0; j <= 18; j++)
        {
            s[0] = s[1] = 0;
            for(int i = l; i <= r; i++)
                res += (1ll << j) * s[!(a[p[i]] >> j & 1)], s[a[p[i]] >> j & 1]++;
        }
        return res;
    }
    int c0 = 0, c1 = 0;
    for(int i = l; i <= r; i++)
    {
        if(c[p[i]] >> k & 1) p1[++c1] = p[i];
        else p0[++c0] = p[i];
    }
    int A[2][20][2] = {0}, B[2][20][2] = {0};
    for(int i = 1; i <= c0; i++)
    {
        int bit = a[p0[i]] >> k & 1;
        for(int j = 0; j <= 18; j++)
        {
            A[bit][j][a[p0[i]] >> j & 1]++;
            B[bit][j][b[p0[i]] >> j & 1]++;
        }
    }
    for(int i = 1; i <= c1; i++)
    {
        int bit = a[p1[i]] >> k & 1;
        for(int j = 0; j <= 18; j++)
        {
            res += (1ll << j) * A[bit][j][!(a[p1[i]] >> j & 1)];
            res += (1ll << j) * B[!bit][j][!(b[p1[i]] >> j & 1)];
        }
    }
    for(int i = 1; i <= c0; i++) p[l + i - 1] = p0[i];
    for(int i = 1; i <= c1; i++) p[l + c0 + i - 1] = p1[i];
    return res + solve(l, l + c0 - 1, k - 1) + solve(l + c0, r, k - 1);
}

ARC127E Priority Queue

倒着从大到小填数,可以发现这样能做到不漏,dp 时只需要考虑这个数是否保留。

考虑每个位置的限制,也就是从当前位置往后至少要 pop 掉多少个数,转移的时候从这个边界开始就行了。

Code
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    cin >> n >> m;
    for(int i = 1; i <= n + m; i++) cin >> a[i];

    int cnt = 0, c1 = 0, c2 = 0;
    for(int i = n + m; i >= 1; i--)
    {
        if(a[i] == 1) cnt++, c1++, lim[cnt] = min(c1, c2);
        else c2++;
        if(c1 > c2) c1 = c2;
    }

    f[0][0] = 1;
    for(int i = 1; i <= n; i++)
        for(int j = lim[i]; j <= m; j++)
            f[i][j] = add(f[i - 1][j - 1] + f[i - 1][j]);
    cout << f[n][m] << '\n';
    return 0;
}

ARC123E Training

\(f(n)=A_x+\lfloor\dfrac{n}{B_x}\rfloor\)\(g(n)=A_y+\lfloor\dfrac{n}{B_y}\rfloor\)

先去掉下取整,不难发现两个函数都是递增的,并且答案只会出现在 \(|f(x)-g(x)|\le 1\) 的位置,那么确定出这段区间,用总的减去不合法的。

不妨设 \(f(x)-g(x)\le 1\) 的区间为 \([l,r]\),由于差最大为 \(1\),带上下取整后差为 \(0/1\),那么答案即为 \(r-l+1-\sum_{i=l}^r(f(i)-g(i))\)

\(g(x)-f(x)\le 1\) 同理。

Code
int sum(int a, int n)
{
    int b = n / a;
    return b * (b - 1) / 2 * a + (n % a + 1) * b;
}

int calc(int a, int b, int l, int r)
{
    return a * (r - l + 1) + sum(b, r) - sum(b, l - 1);
}

void solve()
{
    int n, a, b, c, d, ans = 0;
    cin >> n >> a >> b >> c >> d;

    if(b == d) return cout << (a == c ? n : 0) << '\n', void();
    if(b < d) swap(a, c), swap(b, d);

    int l = max(0ll, b * d * (a - c - 1) / (b - d)) + 1;    // a + n / b = c + n / d + 1
    int r = min(n, b * d * (a - c) / (b - d));              // a + n / b = c + n / d
    if(l <= r) ans += r - l + 1 - (calc(a, b, l, r) - calc(c, d, l, r));

    l = max(0ll, b * d * (a - c) / (b - d)) + 1;            // a + n / b = c + n / d
    r = min(n, b * d * (a - c + 1) / (b - d));              // a + n / b = c + n / d - 1
    if(l <= r) ans += r - l + 1 - (calc(c, d, l, r) - calc(a, b, l, r));

    cout << ans << '\n';
    return;
}

ARC128D Neq Neq

如果出现相邻两个相等的,这两个都删不掉,所以可以将原序列分成若干段,单独计算。

\(f_i\) 表示前 \(i\) 个数操作完的方案数,首先 \(f_i\leftarrow f_{i-1}+f_{i-2}\),然后如果出现三个不同的数,那么找到最后一个位置 \(j\) 满足 \([j,i]\) 有三个不同的数,\(j\) 相邻两个数可以任意删,所以 \(f_i\leftarrow g_j\),其中 \(g_j\) 为前缀和。

Code
int solve(int l, int r)
{
    a[l - 1] = 0;
    for(int i = l - 1; i <= r; i++)
        cnt[a[i]] = 0, f[i] = g[i] = 0;
    cnt[a[l]] = 1; num = 1;
    f[l] = 1, g[l] = 1;
    for(int i = l + 1, j = l - 1; i <= r; i++)
    {
        if(!cnt[a[i]]) num++;
        cnt[a[i]]++;
        f[i] = add(f[i - 1] + f[i - 2]);
        if(num >= 3)
        {
            while(num >= 3 && j < i - 3)
            {
                if(!--cnt[a[j]]) num--;
                j++;
            }
            if(num < 3) j--, cnt[a[j]]++, num++;
            f[i] = add(f[i] + g[j]);
        }
        g[i] = add(g[i - 1] + f[i]);
    }
    return f[r];
}

ARC128E K Different Values

\(m=\sum a_i\),每 \(k\) 个分成一块,最后一块为 \(p\le k\),设有 \(q\) 块,\(c=\sum[a_i=q]\) 那么有解的充要条件是:\(\forall a_i\le q,c\le p\)

然后一个一个填数,在当前位置,若 \(c=p\),我们只能填这 \(c\) 个数中的某一个,否则,可以填任意一个合法的。

ARC128F Game against Robot

格路计数神仙题!

「解题报告」ARC128F Game against Robot - APJifengc - 博客园 (cnblogs.com)

ARC130E Increasing Minimum

\(i\) 序列分段,使每一段内的 \(a_i\) 是相同的,不难发现一个合法的 \(i\) 序列除掉最后一段的划分是唯一的,每一段一定包含上一段的所有数,并且对应一个唯一的 \(a\) 序列,

那么设 \(f_i\) 表示前 \(i\) 个数最少分成多少段,转移 \(f_i=f_{i-cnt}+1\)\(cnt\) 为前 \(i\) 个数有多少个不同的。

枚举最后一段,找到 \(f_{p}\) 最小并且最靠后的位置,然后计算 \(a\) 序列即可。

\(a_i\) 初值为 \(f_p+1\),然后每出现一次 \(i\)\(a_i\leftarrow a_i-1\)

Code
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    cin >> n >> k;
    for(int i = 1; i <= k; i++) cin >> p[i];
    
    int pre = 0, cnt = 0;
    for(int i = 1; i <= k; i++)
    {
        pre = max(pre, lst[p[i]]);
        if(!lst[p[i]]) cnt++;
        lst[p[i]] = i;
        if(pre == i - cnt) f[i] = f[i - cnt] + 1;
        else f[i] = 1e9;
    }

    int pos = -1;
    for(int i = pre; i <= k; i++)
        if(pos == -1 || f[i] <= f[pos]) pos = i;
    if(f[pos] == 1e9) return cout << "-1\n", 0;

    for(int i = 1; i <= n; i++) a[i] = f[pos] + 1;
    for(int i = 1; i <= pos; i++) a[p[i]]--;
    for(int i = 1; i <= n; i++) cout << a[i] << " \n"[i == n];
    return 0;
}

ARC132E Paw

最终状态一定是存在相邻两个洞,左边都是 <,中间的不变,右边都是 >

\(f_i\) 表示还有 \(i\) 个洞,操作完后都是 < 的概率,那么转移是 \(f_i=f_{i-1}\times \dfrac{2i-1}{2i}\)

这是因为任选一个洞,只有最右边的那个洞不能向右走。都是 > 的概率相同。

计算答案枚举相邻两个洞,求出 < 的个数乘上概率即可。

Code
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);

    cin >> n >> (s + 1);

    inv[1] = 1;
    for(int i = 2; i <= n << 1; i++)
        inv[i] = mod - 1ll * mod / i * inv[mod % i] % mod;
    f[0] = 1;
    for(int i = 1; i <= n; i++)
        f[i] = 1ll * f[i - 1] * sub(1 - inv[i << 1]) % mod;

    for(int i = 1; i <= n; i++)
        if(s[i] == '.') p[++m] = i;
    p[0] = 0, p[m + 1] = n + 1;

    int ans = 0;
    for(int i = 0; i <= m; i++)
    {
        int cnt = 0;
        for(int j = p[i] + 1; j <= p[i + 1] - 1; j++) if(s[j] == '<') cnt++;
        ans = add(ans + 1ll * (cnt + p[i]) * f[i] % mod * f[m - i] % mod);
    }
    cout << ans << '\n';
    return 0;
}

ARC158E All Pair Shortest Paths

最短路显然不会往回走,那么考虑分治,统计经过 \(mid\) 的所有最短路和。

假设左边某点到 \((mid,0)\)\((mid,1)\) 的最短路分别为 \(a,b\),右边某点到 \((mid+1,0)\)\((mid+1,1)\) 的最短路分别为 \(c,d\),那么答案为 \(\sum \min(a+c,b+d)\)

\(a+c<b+d\) 时取 \(a+c\),即 \(a-b<d-c\),排序后维护指针即可。

\(a+c\ge b+d\) 时同理。

Code
void solve(int l, int r)
{
    if(l == r) return ans = add(ans + 3ll * (a[l] + b[l]) % mod), void();
    int mid = (l + r) >> 1;
    solve(l, mid), solve(mid + 1, r);

    int c1 = 0, c2 = 0;
    f[mid][0] = a[mid], f[mid][1] = a[mid] + b[mid];
    g[mid][0] = b[mid] + a[mid], g[mid][1] = b[mid];
    for(int i = mid - 1; i >= l; i--)
    {
        f[i][0] = min(f[i + 1][0], g[i + 1][0] + b[i]) + a[i];
        f[i][1] = min(f[i + 1][1], g[i + 1][1] + b[i]) + a[i];
        g[i][0] = min(g[i + 1][0], f[i + 1][0] + a[i]) + b[i];
        g[i][1] = min(g[i + 1][1], f[i + 1][1] + a[i]) + b[i];
    }

    f[mid + 1][0] = a[mid + 1], f[mid + 1][1] = a[mid + 1] + b[mid + 1];
    g[mid + 1][0] = b[mid + 1] + a[mid + 1], g[mid + 1][1] = b[mid + 1];
    for(int i = mid + 2; i <= r; i++)
    {
        f[i][0] = min(f[i - 1][0], g[i - 1][0] + b[i]) + a[i];
        f[i][1] = min(f[i - 1][1], g[i - 1][1] + b[i]) + a[i];
        g[i][0] = min(g[i - 1][0], f[i - 1][0] + a[i]) + b[i];
        g[i][1] = min(g[i - 1][1], f[i - 1][1] + a[i]) + b[i];
    }

    for(int i = l; i <= mid; i++)
    {
        p1[++c1] = {f[i][0], f[i][1], f[i][0] - f[i][1]};
        p1[++c1] = {g[i][0], g[i][1], g[i][0] - g[i][1]};
    }
    for(int i = mid + 1; i <= r; i++)
    {
        p2[++c2] = {f[i][0], f[i][1], f[i][1] - f[i][0]};
        p2[++c2] = {g[i][0], g[i][1], g[i][1] - g[i][0]};
    }

    sort(p1 + 1, p1 + c1 + 1, cmp);
    sort(p2 + 1, p2 + c2 + 1, cmp);

    for(int j = 1, i = 1, sum = 0; j <= c2; j++)
    {
        while(i <= c1 && p1[i].c < p2[j].c) sum = add(sum + p1[i].a % mod), i++;
        ans = add(ans + 2ll * (sum + p2[j].a % mod * (i - 1) % mod) % mod);
    }
    for(int i = 1, j = 1, sum = 0; i <= c1; i++)
    {
        while(j <= c2 && p2[j].c <= p1[i].c) sum = add(sum + p2[j].b % mod), j++;
        ans = add(ans + 2ll * (sum + p1[i].b % mod * (j - 1) % mod) % mod);
    }
    return;
}
posted @ 2023-04-14 19:44  Acestar  阅读(93)  评论(3编辑  收藏  举报