省队集训-0419试机赛

· · 个人记录

A.石堆分裂

题意:

初始时有一个共 n 个石子的石堆。给定 m,一次操作时,假设有 k 个石堆,大小为 a_{1...k},可以指定 b_{1...k},满足 0\le b_i\le a_i,\sum b_i\le m,然后把第 i 堆分裂成 b_i,a_i-b_i 两堆,大小为 0 的当作不存在。求分裂出 n 个大小为 1 的石堆所需的最小操作次数。
多组测试,T\le10^3,n\le m\le10^9

Sol:

假设要分裂 x 次,则相当于要给 n 个石子都赋一个 [0,2^x) 的编号,每一位表示在这一次操作中这颗石子是分到 b_i 一堆还是 a_i-b_i 一堆,要求这些编号两两不同,且每一位 1 的数量都不超过 m
考虑二分答案。按照 popcount 贪心,从小到大填入。popcount 相同的数,如果填不够 n 个,给每一位的贡献都是相同的,即 \binom{x-1}{i-1};否则可以把 1 均摊到每一位,这样一定最优。
复杂度 O(T\log^2n)

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int T,n,m;
bool check(int x)
{
    ll sum = 0,cnt = 0,c1 = 1,c2 = 1;
    for(int i = 0;i <= x;i++)
    {
        if(i) c1 = c1 * (x - i + 1) / i;
        if(i > 1) c2 = c2 * (x - i + 1) / (i - 1);
        if(sum + c1 >= n) return cnt + (i ? ((n - sum) * i + x - 1) / x : 0) <= m;
        sum += c1,cnt += (i ? c2 : 0);
        if(cnt > m) return false;
    }
    return false;
}
int main()
{
    freopen("split.in","r",stdin);
    freopen("split.out","w",stdout);
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        int l = -1,r = n,mid;
        while(r - l > 1)
        {
            mid = (l + r) / 2;
            if(check(mid)) r = mid;
            else l = mid;
        }
        printf("%d\n",r);
    }
    return 0;
}

白:

题意:

有一个排列,可以执行一次操作:选择若干个不交的区间,然后把区间里的数从小到大排序。也可以选择什么都不做。问可以得到多少本质不同的排列。

#### Sol: 相当于把排列划成若干段,每一段排序。由于要求本质不同,我们要求划出来的区间都是极小的,即每一段都不存在一个分界点,使得左边最大值小于右边最小值。 设 $f_i$ 表示 $[1,i]$ 的答案。可以把合法的段都拿出来,然后 $O(n^2)$ DP。考虑优化。 设 $x$ 为 $i$ 之前最靠后的小于 $p_i$ 的位置,则 $(x,i]$ 都可以是 $i$ 这一段的左端点;$y$ 为 $x$ 之前最靠后的大于 $p_i$ 的位置,则 $(y,x]$ 处都不能是左端点。 考虑 $y$ 之前的点作为左端点,充要条件为 $[l,y]$ 中不存在一个分界点,使得 $[l,mid]$ 的最大值小于 $(mid,i]$ 的最小值。设 $z$ 为 $[y,x]$ 内 $p$ 最小值的位置,发现 $(z,i]$ 都大于 $p_z$,所以 $i$ 的合法左端点对于 $z$ 而言都是合法的,但反过来不一定,因为 $z$ 的合法左端点可能会落在 $(y,z]$ 中。由于 $[y,z)$ 都大于 $p_z$,所以可以确定 $(y,z]$ 都可以转移到 $f_z$。因此我们让 $f_i$ 加上 $f_z$ 再减掉 $(y,z]$ 处带来的贡献即可。 树状数组维护,单点修改区间和,$O(n\log n)$。 #### Code: ~~~cpp #include <bits/stdc++.h> #define lb(x) ((x) & (-(x))) using namespace std; const int N = 2e5 + 5,mod = 998244353; int n,p[N],q[N],f[N],lst1[N],lst2[N],st[N][20]; set <int> s; set <int> :: iterator it; bool cmp(int x,int y){return p[x] < p[y];} struct Bit { int s[N]; void update(int x,int v){for(int i = x;i <= n;i += lb(i)) s[i] = (s[i] + v) % mod;} int query(int x) { int ret = 0; for(int i = x;i;i -= lb(i)) ret = (ret + s[i]) % mod; return ret; } } tr; int main() { freopen("bai.in","r",stdin); freopen("bai.out","w",stdout); scanf("%d",&n); for(int i = 1;i <= n;i++) scanf("%d",&p[i]); for(int i = 1;i <= n;i++) q[i] = i; sort(q + 1,q + n + 1,cmp); for(int i = 1;i <= n;i++) { it = s.lower_bound(q[i]); if(it != s.begin()) lst1[q[i]] = (*prev(it)); s.insert(q[i]); } s.clear(); for(int i = n;i >= 1;i--) { it = s.lower_bound(lst1[q[i]]); if(it != s.begin()) lst2[q[i]] = (*prev(it)); s.insert(q[i]); } for(int i = 1;i <= n;i++) st[i][0] = i; for(int j = 1;j < 20;j++) for(int i = 1,x,y;i + (1 << j) - 1 <= n;i++) { x = st[i][j - 1],y = st[i + (1 << (j - 1))][j - 1]; st[i][j] = (p[x] < p[y] ? x : y); } f[0] = 1; tr.update(1,1); for(int i = 1;i <= n;i++) { f[i] = (tr.query(i) - tr.query(lst1[i]) + mod) % mod; if(lst2[i]) { int l = lst2[i] + 1,r = lst1[i],t = (int)__lg(r - l + 1),x = st[l][t],y = st[r - (1 << t) + 1][t],m = (p[x] < p[y] ? x : y); f[i] = (f[i] + f[m]) % mod; f[i] = (f[i] - tr.query(m) + mod) % mod; f[i] = (f[i] + tr.query(lst2[i])) % mod; } tr.update(i + 1,f[i]); } printf("%d\n",f[n]); return 0; } ~~~