杭电多校第八场8月12日补题记录

A X-liked Counting

题意:给定区间 [ l , r ] [l,r] [l,r] 与 模数 k k k,问区间中不含数码 7 7 7 并且这个数字的某一个前缀或者后缀能被 k k k 整除的数字的数目,如 123 123 123 对于模数 4 4 4 满足这一性质因为其具有 12 12 12 的前缀并且 4 ∣ 12 4|12 412 k ≤ 500 k \leq 500 k500 l , r ≤ 1 × 1 0 18 l,r \leq 1\times 10^{18} l,r1×1018

解法:我们上来肯定想直接求出前后缀条件的数字个数,但是存在条件是非常难处理的,因而考虑反选,找到全部的数字个数再减去前后缀都不能整除 k k k 的数字数目。

首先先求出不含数码 7 7 7​​ 的数字个数,可以使用一个简单的数位 DP 或者直接公式计算出来。下面给出了数位 DP 的方法:

long long tot[25][15];
long long cal(long long x)
{
    if(x==0)
        return 1;
    int cnt = 0, num[25] = {0};
    while(x)
    {
        num[++cnt] = x % 10;
        x /= 10;
    }
    long long ans = 0;
    for (int i = cnt; i >= 1;i--)
    {
        for (int j = 0; (i > 1 && j < num[i]) || (i == 1 && j <= num[i]); j++)
            ans += tot[i][j];
        if(num[i]==7)
            break;
    }
    return ans;
}
void get_tot()
{
    for (int i = 1; i <= 9; i++)
        if (i != 7)
            tot[1][i] = 1;
    tot[1][0] = 1;
    for (int i = 2; i <= 19;i++)
    {
        long long temp = 0;
        for (int j = 0; j <= 9;j++)
            temp += tot[i - 1][j];
        for (int j = 0; j <= 9;j++)
        {
            if(j==7)
                continue;
            tot[i][j] = temp;
        }
    }
}

接下来就是找到前后缀都不能整除 k k k 的数字个数。下文中的前后缀和都是表示在模 k k k​ 意义下的。考虑 DP,从高位到低位考虑。因为现在既要维护前缀条件又要维护后缀条件,而这是非常麻烦的,并且如果双向推进会存在重复计算等问题,因而不妨枚举一个整体,然后单向顺次递推某一项(如前缀),另一项通过计算得出。不妨设推进的是前缀和,那么当整体模数为 x x x 时,前缀和为 y y y,其枚举位数为 p p p,则后缀和为 x − y × 1 0 p x-y\times 10^p xy×10p。我们对于后缀和只需要判断 x ≠ y × 1 0 p x \neq y \times 10^p x=y×10p 即可。

注意到很多状态无用,因而注意剪枝即可。稍微有些卡常。

代码中展示了另一种计算不含 7 7 7 数码的方式——只需要这一位上能填什么数码即可。

#include <cstdio>
#include <algorithm>
#include <memory.h>
using namespace std;
long long f[20][505][2][2];
long long th[25], power_9[25];
long long cal(long long x,int k)
{
    int cnt = 0, num[25] = {0};
    while(x)
    {
        num[++cnt] = x % 10;
        x /= 10;
    }
    long long ans = 1;
    for (int i = cnt; i >= 1; i--)
    {
        int now = 0;
        for (int j = 0; j < num[i]; j++)
            now += (j != 7);
        ans += power_9[i - 1] * now;
        if(num[i]==7)
            break;
    }
    for (int i = 1; i <= cnt; i++)
        if(num[i]==7)
        {
            ans--;
            break;
        }
    for (int tot_mod = 1; tot_mod < k;tot_mod++)//整体模数
    {
        for (int i = 0; i <= cnt; i++)
            for (int j = 0; j < k; j++)
                f[i][j][0][0] = f[i][j][0][1] = f[i][j][1][0] = f[i][j][1][1] = 0;
        f[cnt][0][1][1] = 1;
        for (int place = cnt; place >= 1; place--)//数位
            for (int prefix = 0; prefix < k;prefix++)
                for (int limit = 0; limit <= 1;limit++)//是否达到上界
                    for (int leading_zero = 0; leading_zero <= 1;leading_zero++)// 此位为 1 表示现在仍然是前缀全为0的情况
                    {
                        if(!f[place][prefix][limit][leading_zero])
                            continue;
                        int up;
                        if(limit)
                            up = num[place];
                        else
                            up = 9;
                        for (int digit = 0; digit <= up;digit++)
                        {
                            if(digit==7)
                                continue;
                            int newprefix = (prefix * 10 + digit) % k;
                            if(digit || !leading_zero)
                                if (newprefix == 0 || (place > 1 && (newprefix * th[place - 1] % k) % k == tot_mod))//注意当place=1时不能判定后一个条件——此时前缀一定等于全部的取模结果
                                    continue;
                            f[place - 1][newprefix][limit & (digit == up)][leading_zero & !(digit)] += f[place][prefix][limit][leading_zero];
                        }
                    }
        ans -= f[0][tot_mod][1][0] + f[0][tot_mod][0][0];
    }
    return ans;
}
int main()
{
    int t, x;
    long long l, r;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%lld%lld%d", &l, &r, &x);
        th[0] = power_9[0] = 1;
        for (int i = 1; i <= 19;i++)
            th[i] = th[i - 1] * 10 % x;
        for (int i = 1; i <= 19;i++)
            power_9[i] = power_9[i - 1] * 9;
        printf("%lld\n", cal(r, x) - cal(l - 1, x));
    }
    return 0;
}

B Buying Snacks

题意:有 n n n​ 种零食,每一种只能至多买一次,或大份或小份,每一种大份都是 2 2 2​ 块钱,小份 1 1 1​ 块钱。允许相邻两种零食捆绑销售,无论大小份,只要绑定销售就是减 1 1 1 块钱。给定 m m m,问 ∀ k ∈ [ 1 , m ] \forall k \in [1,m] k[1,m]​ 中恰好花完 k k k​ 块钱的方案数。 n ≤ 1 × 1 0 9 n \leq 1\times 10^9 n1×109 m ≤ 2 × 1 0 4 m \leq 2\times 10^4 m2×104

解法:需要求很多情况的方案数一律考虑生成函数。

f i , 0 ( x ) f_{i,0}(x) fi,0(x) 表示前 i i i 个物品,且最后一个物品(即第 i i i 件物品)没有买的花钱方案, f i , 1 ( x ) f_{i,1}(x) fi,1(x) 表示前 i i i 个物品最后一个物品单买没有组成捆绑销售的花钱方案, f i , 2 ( x ) f_{i,2}(x) fi,2(x) 表示前 i i i 个物品,最后一个物品与前一个物品组成捆绑销售的方案。容易写出以下的递推:

f i , 0 ( x ) = f i − 1 , 0 ( x ) + f i − 1 , 1 ( x ) + f i − 1 , 2 ( x ) (1) f_{i,0}(x)=f_{i-1,0}(x)+f_{i-1,1}(x)+f_{i-1,2}(x) \tag{1} fi,0(x)=fi1,0(x)+fi1,1(x)+fi1,2(x)(1)

f i , 1 ( x ) = ( 1 + x + x 2 ) ( f i − 1 , 0 ( x ) + f i − 1 , 1 ( x ) + f i − 1 , 2 ( x ) ) (2) f_{i,1}(x)=(1+x+x^2)(f_{i-1,0}(x)+f_{i-1,1}(x)+f_{i-1,2}(x)) \tag{2} fi,1(x)=(1+x+x2)(fi1,0(x)+fi1,1(x)+fi1,2(x))(2)

f i , 2 ( x ) = ( 1 + x ) f i − 1 , 1 ( x ) (3) f_{i,2}(x)=(1+x)f_{i-1,1}(x) \tag{3} fi,2(x)=(1+x)fi1,1(x)(3)

( 1 ) (1) (1) 当前不买,则和上一位买不买、以什么方式买都无关。

( 2 ) (2) (2) 这一位原价买,那和上一位也没啥关系。

( 3 ) (3) (3) 这一位要绑定,那必须上一位买了,并且还得没绑定销售。

容易发现, f i , 1 ( x ) = ( 1 + x + x 2 ) f i , 0 f_{i,1}(x)=(1+x+x^2)f_{i,0} fi,1(x)=(1+x+x2)fi,0。进一步的,可以将原式写成一个二阶递推式—— f i , 0 ( x ) = ( x 2 + x + 1 ) f i − 1 , 0 ( x ) + ( 1 + x ) ( x + x 2 ) f i − 2 , 0 ( x ) f_{i,0}(x)=(x^2+x+1)f_{i-1,0}(x)+(1+x)(x+x^2)f_{i-2,0}(x) fi,0(x)=(x2+x+1)fi1,0(x)+(1+x)(x+x2)fi2,0(x)。将 f i , 0 ( x ) f_{i,0}(x) fi,0(x) 简记为 F i ( x ) F_i(x) Fi(x)​。​容易注意到,最终即是求 f n , 0 ( x ) + f n , 1 ( x ) + f n , 2 ( x ) = f n + 1 , 0 ( x ) = F n + 1 ( x ) f_{n,0}(x)+f_{n,1}(x)+f_{n,2}(x)=f_{n+1,0}(x)=F_{n+1}(x) fn,0(x)+fn,1(x)+fn,2(x)=fn+1,0(x)=Fn+1(x)​​。

此递推的边界条件为 F 0 ( x ) = 1 F_0(x)=1 F0(x)=1 F 1 ( x ) = 1 + x + x 2 F_{1}(x)=1+x+x^2 F1(x)=1+x+x2

对于朴素二阶线性递推—— f i = a f i − 1 + b i − 2 f_{i}=af_{i-1}+b_{i-2} fi=afi1+bi2,如果想快速递推,常用的矩阵快速幂,构造矩阵 A = [ 0 a 1 b ] A=\begin{bmatrix} 0 & a \\ 1 & b \\ \end{bmatrix} A=[01ab],然后求 A n A^n An,这样的复杂度是 O ( k 3 log ⁡ n ) O(k^3 \log n) O(k3logn) 的, k k k 为矩阵的大小,此处为 2 2 2。此处也可以使用这一方法——构造转移矩阵。构造矩阵 A = A = [ 0 x 2 + x + 1 1 ( x + 1 ) ( x + x 2 ) ] A=A=\begin{bmatrix} 0 & x^2+x+1 \\ 1 & (x+1)(x+x^2) \\ \end{bmatrix} A=A=[01x2+x+1(x+1)(x+x2)],此时单次矩阵乘法复杂度变为 O ( k 3 m log ⁡ m ) O(k^3 m \log m) O(k3mlogm),整体复杂度为 O ( k 3 m log ⁡ m log ⁡ n ) O(k^3 m \log m \log n) O(k3mlogmlogn)​。如果是正常数据此题可以通过,​但是此题有些卡常。因而可以做一个优化——在多项式乘法过程中只进行 D F T DFT DFT 而不在 O ( m ) O(m) O(m) 乘完之后再做 I D F T IDFT IDFT,等到全部计算完成再做 I D F T IDFT IDFT。这样可以极大的优化常数——仅原版的一半。

A n A^n An 的计算上,还有另一个方法——计算特征值,转化成对角阵从而快速计算,此法等效于求解通项公式。对于朴素二阶递推,其通解形式一定为 C 1 p n + C 2 q n C_1p^n+C_2q^n C1pn+C2qn​,猜测此处多项式也有类似结论。

f ( x ) = 1 + x + x 2 f(x)=1+x+x^2 f(x)=1+x+x2​, g ( x ) = ( 1 + x ) ( x + x 2 ) g(x)=(1+x)(x+x^2) g(x)=(1+x)(x+x2)​​, F n ( x ) = f ( x ) F n − 1 ( x ) + g ( x ) F n − 2 ( x ) F_{n}(x)=f(x)F_{n-1}(x)+g(x)F_{n-2}(x) Fn(x)=f(x)Fn1(x)+g(x)Fn2(x),其特征多项式 h ( λ ) = λ 2 − f ( x ) λ − g ( x ) h(\lambda)=\lambda^2-f(x)\lambda-g(x) h(λ)=λ2f(x)λg(x),注意此处 λ \lambda λ 为一个 x x x 的多项式。求解 h ( λ ) = 0 h(\lambda)=0 h(λ)=0,得 λ 1 = ( 1 + x ) 2 \lambda_1=(1+x)^2 λ1=(1+x)2 λ 2 = − x \lambda_2=-x λ2=x

带入 F 0 ( x ) = 1 , F 1 ( x ) = 1 + x + x 2 F_{0}(x)=1,F_1(x)=1+x+x^2 F0(x)=1,F1(x)=1+x+x2​,可以解得 C 1 = ( 1 + x ) 2 1 + 3 x + x 2 , C 2 = x 1 + 3 x + x 2 \displaystyle C_1=\frac{(1+x)^2}{1+3x+x^2},C_2=\frac{x}{1+3x+x^2} C1=1+3x+x2(1+x)2,C2=1+3x+x2x​​,​则通解形式为 F n ( x ) = ( 1 + x ) 2 n + 2 − ( − x ) n + 1 1 + 3 x + x 2 \displaystyle F_n(x)=\frac{(1+x)^{2n+2}-(-x)^{n+1}}{1+3x+x^2} Fn(x)=1+3x+x2(1+x)2n+2(x)n+1​。接下来只需要多项式求逆与 O ( m ) O(m) O(m)​ 的时间推出 ( n m ) \dbinom{n}{m} (mn)​ 即可在 O ( m log ⁡ m ) O(m \log m) O(mlogm)​​ 时间内解决此题。

C Ink on paper

题意:一个无限大平面上有 n n n 个点,每个点以 0.5 0.5 0.5 的速度扩张,问最早何时这些点扩张形成的圆能互相有交。 n ≤ 5 × 1 0 3 n \leq 5\times 10^3 n5×103

解法:抽象出来即是 K n K_n Kn​ 上找到一个最小生成树,边权为点间距,求最小生成树上最大的边权。此题使用 Kruskal 可以在 O ( n 2 log ⁡ n ) O(n^2 \log n) O(n2logn) 的时间内通过,但是这并不优并且容易被卡常。使用 Prim 算法可以在 O ( n 2 ) O(n^2) O(n2) 的时间通过。其原理即是找到已加入点到未加入点的最小距离。

#include <cstdio>
#include <algorithm>
using namespace std;
const long long inf = 0x3f3f3f3f3f3f3f3f;
int father[5005];
int getfather(int x)
{
    return father[x] == x ? x : father[x] = getfather(father[x]);
}
struct node
{
    long long x;
    long long y;
};
struct node pt[5005];
long long dist(node a,node b)
{
    return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
long long dis[5005];
bool vis[5005];
int main()
{
    int n, t;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        for (int i = 1; i <= n;i++)
            vis[i] = 0;
        for (int i = 1; i <= n; i++)
            scanf("%lld%lld", &pt[i].x, &pt[i].y);
        long long ans = 0;
        for (int i = 1; i <= n;i++)
            dis[i] = dist(pt[1], pt[i]);
        int pos = 1;
        vis[1] = 1;
        for (int i = 1; i < n;i++)
        {
            long long minimum = inf;
            for (int j = 1; j <= n;j++)
                if(!vis[j] && dis[j]<minimum)
                {
                    minimum = dis[j];
                    pos = j;
                }
            ans = max(ans, dis[pos]);
            vis[pos] = 1;
            for (int j = 1; j <= n;j++)
                if(!vis[j])
                    dis[j] = min(dis[j], dist(pt[pos], pt[j]));
        }
        printf("%lld\n", ans);
    }
    return 0;
}

D Counting Stars

题意:给定一个数列 { a n } \{ a_n\} {an},满足 a i ≤ 1 × 1 0 9 a_i \leq 1\times 10^9 ai1×109。有如下操作:

  1. 给定区间 [ l , r ] [l,r] [l,r],区间每一个数二进制最高位乘以二。
  2. 给定区间 [ l , r ] [l,r] [l,r],区间每一个数二进制最后一位被减掉。
  3. 给定区间 [ l , r ] [l,r] [l,r],求 ∑ i = l r a i \displaystyle \sum_{i=l}^{r}a_i i=lrai​。

解法:考虑到操作一只对最高位处理,那么很自然的想到分别维护最高位的和与剩余低位的和。考虑到初始的 a i ≤ 1 × 1 0 9 a_i \leq 1 \times 10^9 ai1×109,则每一个数能够执行操作二的次数不超过 log ⁡ a i \log a_i logai 级别。对于整个数列,也就只有约 O ( 30 n ) O(30n) O(30n) 次操作。这里和区间不断开根号、区间不断取欧拉函数是一样的,利用其操作次数的有限性,从而可以允许暴力的存在,即暴力的对每一个数减去最低位。

具体操作中,维护区间二进制位数最大值,区间最高位的和,区间剩余低位的和,与乘以二的 tag。进行操作二时,如果只剩一个数,那么要减的时高位的和,反之减低位的和。当区间二进制位最多的数都已然是 0 0 0 则停止操作。

整体复杂度 O ( q log ⁡ n + n log ⁡ N ) O(q \log n+n \log N) O(qlogn+nlogN)

#include <cstdio>
#include <algorithm>
using namespace std;
const long long mod = 998244353ll;
const int N = 100000;
struct node
{
    int digit;
    long long sum_high;
    long long sum_low;
    long long tag;
};
struct node t[4 * N + 5];
long long a[N + 5];
int lowbit(int x)
{
    return x & (-x);
}
void pushup(int place)
{
    t[place].sum_high = (t[place << 1].sum_high + t[place << 1 | 1].sum_high) % mod;
    t[place].sum_low = (t[place << 1].sum_low + t[place << 1 | 1].sum_low) % mod;
    t[place].digit = max(t[place << 1].digit, t[place << 1 | 1].digit);
}
void pushdown(int place,int left,int right)
{
    if(t[place].tag!=1)
    {
        t[place << 1].sum_high = (t[place << 1].sum_high * t[place].tag) % mod;
        t[place << 1].tag = (t[place << 1].tag * t[place].tag) % mod;
        t[place << 1 | 1].sum_high = (t[place << 1 | 1].sum_high * t[place].tag) % mod;
        t[place << 1 | 1].tag = (t[place << 1 | 1].tag * t[place].tag) % mod;
        t[place].tag = 1;
    }
}
void build(int place, int left, int right)
{
    t[place].sum_high = t[place].sum_low = 0;
    t[place].tag = 1;
    t[place].digit = 0;
    if(left==right)
    {
        bool flag = 0;
        for (int i = 30; i >= 0;i--)
            if(a[left]&(1<<i))
            {
                t[place].digit++;
                if(!flag)
                {
                    t[place].sum_high = (1 << i) % mod;
                    flag = 1;
                }
                else
                    t[place].sum_low = (t[place].sum_low + (1 << i)) % mod;
            }
        return;
    }
    int mid = (left + right) >> 1;
    build(place << 1, left, mid);
    build(place << 1 | 1, mid + 1, right);
    pushup(place);
}
void update1(int place,int left,int right,int start,int end)
{
    if(t[place].digit==0)
        return;
    if(left==right)
    {
        t[place].digit--;
        if (t[place].digit == 0)
            t[place].sum_high = 0;
        else
        {
            long long temp = lowbit(a[left]);
            a[left] -= temp;
            t[place].sum_low -= temp;
        }
        return;
    }
    pushdown(place, left, right);
    int mid = (left + right) >> 1;
    if(start<=mid)
        update1(place << 1, left, mid, start, end);
    if(end>mid)
        update1(place << 1 | 1, mid + 1, right, start, end);
    pushup(place);
}
void update2(int place,int left,int right,int start,int end)
{
    if(start<=left && right<=end)
    {
        t[place].sum_high = (t[place].sum_high * 2) % mod;
        t[place].tag = (t[place].tag * 2) % mod;
        return;
    }
    pushdown(place, left, right);
    int mid = (left + right) >> 1;
    if(start<=mid)
        update2(place << 1, left, mid, start, end);
    if(end>mid)
        update2(place << 1 | 1, mid + 1, right, start, end);
    pushup(place);
}
long long query(int place, int left,int right,int start,int end)
{
    if(start<=left && right<=end)
        return t[place].sum_high + t[place].sum_low;
    pushdown(place, left, right);
    int mid = (left + right) >> 1;
    long long ans = 0;
    if(start<=mid)
        ans = (ans + query(place << 1, left, mid, start, end)) % mod;
    if(end>mid)
        ans = (ans + query(place << 1 | 1, mid + 1, right, start, end)) % mod;
    return ans;
}
int main()
{
    int t, n, q;
    scanf("%d", &t);
    while(t--)
    {
        int op, l, r;
        scanf("%d", &n);
        for (int i = 1; i <= n;i++)
            scanf("%lld", &a[i]);
        build(1, 1, n);
        scanf("%d", &q);
        while(q--)
        {
            scanf("%d%d%d", &op, &l, &r);
            switch(op)
            {
                case 1:
                {
                    printf("%lld\n", query(1, 1, n, l, r));
                    break;
                }
                case 2:
                {
                    update1(1, 1, n, l, r);
                    break;
                }
                case 3:
                {
                    update2(1, 1, n, l, r);
                    break;
                }
            }
        }
    }
    return 0;
}

E Separated Number

题意:给定一个长度为 n n n 的数,要求将这个数划分至多 k k k​ 次,每一种划分的权值为划分成的 k k k 段的和。如 11 ∣ 451 ∣ 4 = 11 + 451 + 4 = 466 11|451|4=11+451+4=466 114514=11+451+4=466。问所有这些划分的总和。 n , k ≤ 1 × 1 0 6 n,k \leq 1\times 10^6 n,k1×106

解法:注意到这种划分方式等效于给每个数码一个 1 0 x 10^x 10x​ 的权值。考虑第 i i i​ 位的数字 a i a_i ai​​ 可能会对答案有哪些贡献。考虑它做第 j + 1 j+1 j+1​ 位的贡献,则它的位权为 1 0 j 10^j 10j​。那么由于它做了第 j + 1 j+1 j+1 位,则其后面 i + j i+j i+j 位必须是一个分界点(需满足 i + j < n i+j<n i+j<n),或者数字末尾(此时即为 i + j = n i+j=n i+j=n)。对于 i + j < n i+j<n i+j<n 的情况,我们还需要在剩下的 n − j − 2 n-j-2 nj2 个位置中选取至多 k − 2 k-2 k2 个分界点,此时方案总数为 ∑ l = 0 k − 2 ( n − j − 2 l ) \displaystyle \sum_{l=0}^{k-2}\dbinom{n-j-2}{l} l=0k2(lnj2)。对于 i + j = n i+j=n i+j=n 的情况,则需要在 n − j − 1 n-j-1 nj1 个位置中找到 ∑ l = 0 k − 1 ( n − j − 1 l ) \displaystyle \sum_{l=0}^{k-1}\dbinom{n-j-1}{l} l=0k1(lnj1)

考虑 F ( a , b ) = ∑ i = 0 b ( a i ) \displaystyle F(a,b)=\sum_{i=0}^{b}\dbinom{a}{i} F(a,b)=i=0b(ia) 如何计算。根据 ( n m ) = ( n m − 1 ) + ( n − 1 m − 1 ) \dbinom{n}{m}=\dbinom{n}{m-1}+\dbinom{n-1}{m-1} (mn)=(m1n)+(m1n1) 容易得到 F ( a , b ) = 2 F ( a − 1 , b ) − ( a − 1 b ) F(a,b)=2F(a-1,b)-\dbinom{a-1}{b} F(a,b)=2F(a1,b)(ba1)。因而可以在 O ( n ) O(n) O(n) 的时间内算出 F ( a , k − 1 ) F(a,k-1) F(a,k1) F ( a , k − 2 ) F(a,k-2) F(a,k2)

对于第 i i i​ 位,其可以满足条件的 j j j​ 的范围为 [ 0 , n − i ] [0,n-i] [0,ni]​。因而这一位的贡献为 a i [ ∑ j = 0 n − i − 1 1 0 j F ( j , k − 2 ) + 1 0 n − i F ( n − i , k − 1 ) ] \displaystyle a_i [\sum_{j=0}^{n-i-1} 10^j F(j,k-2)+10^{n-i}F(n-i,k-1)] ai[j=0ni110jF(j,k2)+10niF(ni,k1)]​。只需要对 1 0 a F ( a , b ) 10^a F(a,b) 10aF(a,b)​ 再做前缀和即可。

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1000000;
const long long mod = 998244353ll;
long long f[N + 5][2], th[N + 5];
//f[i][0]--F(i,k-1);f[i][1]--F(i,k-2)
long long fac[2 * N + 5], invfac[2 * N + 5];
long long power(long long a,long long x)
{
    long long ans = 1;
    while(x)
    {
        if(x&1)
            ans = ans * a % mod;
        a = a * a % mod;
        x >>= 1;
    }
    return ans;
}
long long inv(long long x)
{
    return power(x, mod - 2);
}
long long C(int n,int m)
{
    if(m<0 || n<m)
        return 0;
    else
        return fac[n] * invfac[m] % mod * invfac[n - m] % mod;
}
char a[N + 5];
int main()
{
    int t, n, k;
    fac[0] = fac[1] = 1;
    for (int i = 2; i <= 2 * N; i++)
        fac[i] = fac[i - 1] * i % mod;
    invfac[2 * N] = inv(fac[2 * N]);
    for (int i = 2 * N - 1; i >= 0; i--)
        invfac[i] = invfac[i + 1] * (i + 1) % mod;
    th[0] = 1;
    for (int i = 1; i <= N;i++)
        th[i] = th[i - 1] * 10 % mod;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &k);
        scanf("%s", a + 1);
        n = strlen(a + 1);
        if(k==1)
        {
            long long ans = 0;
            for (int i = 1; i <= n;i++)
            {
                ans = (ans * 10) % mod;
                ans = (ans + a[i] - 48) % mod;
            }
            printf("%lld\n", ans);
            continue;
        }
        for (int op = 0; op <= 1;op++)
        {
            int limit = k - op - 1;
            f[0][op] = 1;
            for (int i = 1; i <= n;i++)
                f[i][op] = (2 * f[i - 1][op] % mod - C(i - 1, limit) + mod) % mod;
        }
        long long sum = 0, ans = 0;
        for (int i = n; i >= 1;i--)
        {
            int num = a[i] - 48;    
            ans = (ans + num * ((f[i - 1][0] * th[n - i] % mod + sum)) % mod % mod) % mod;
            if (i > 1)
                sum = (sum + th[n - i] * f[i - 2][1]) % mod;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

H Square Card

题意:给定两个圆 ( x 1 , y 1 , r 1 ) (x_1,y_1,r_1) (x1,y1,r1) ( x 2 , y 2 , r 2 ) (x_2,y_2,r_2) (x2,y2,r2),现等概率向无限大平面上丢一边长为 a a a 的方片,丢完之后方片可以旋转,若旋转到一定的角度可以使得方片完全包含在圆内即算有效。问丢入第一个圆有效时,丢入第二个圆有效的概率。

解法:考虑何时才算有效,我们只需要关注方片中心的位置即可。最靠边时,弦长等于 a a a,此时方片中心距圆心最远,其距离可以算出等于 ( r 2 − a 2 4 ) − a 2 \displaystyle \sqrt{(r^2-\frac{a^2}{4})}-\frac{a}{2} (r24a2) 2a。因而现在问题变成了——新的两个圆交的面积占第一个圆面积的百分比。使用圆交的模板即可。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const double eps = 1e-10;
const double PI = acos(-1.0);
struct Circle
{
    double x, y;
    double r;
};
double calArea(Circle c1, Circle c2)
{
    double d;
    double s, s1, s2, s3, angle1, angle2, temp;
    d = sqrt((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y));
    if(d>=(c1.r+c2.r))
        return 0;
    if((c1.r-c2.r)>=d)
        return acos(-1.0)*c2.r*c2.r;
    if((c2.r-c1.r)>=d)
        return acos(-1.0)*c1.r*c1.r;

    angle1 = acos((c1.r * c1.r + d * d - c2.r * c2.r) / (2 * c1.r * d));
    angle2 = acos((c2.r * c2.r + d * d - c1.r * c1.r) / (2 * c2.r * d));
    s1 = angle1 * c1.r * c1.r;
    s2 = angle2 * c2.r * c2.r;
    s3 = c1.r * d * sin(angle1);
    s = s1 + s2 - s3;
    return s;
}
double cal(double r,double a)
{
    if (r * r - a * a / 4 <= 0)
        return 0;
    double h = sqrt(r * r - a * a / 4);
    return h - a / 2;
}
int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        double x1, y1, x2, y2, r1, r2, a;
        scanf("%lf%lf%lf%lf%lf%lf", &r1, &x1, &y1, &r2, &x2, &y2);
        scanf("%lf", &a);
        r1 = cal(r1, a);
        r2 = cal(r2, a);
        printf("%.6lf\n", calArea((Circle){x1, y1, r1}, (Circle){x2, y2, r2}) / (PI * r1 * r1));
    }
    return 0;
}

I Singing Superstar

题意:给定长度为 m m m 的模式串, n n n 次询问,每次询问长度不超过 30 30 30 的文本串在模式串中最多不重叠匹配次数。 n , m ≤ 1 × 1 0 5 n,m \leq 1\times 10^5 n,m1×105

解法:如果没有不重叠条件那么就是朴素的 AC 自动机。考虑到询问的文本串不是很长,因而可以把全部的长度不超过 30 30 30 的模式串子串放入 Trie 树中。

在统计次数的时候,使用一个方法——新增一个 last 变量,表示至少在 last 之后的位上这一子串才能重新计算。

整体复杂度 O ( 30 n ) O(30n) O(30n)

#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int N = 100000;
struct node
{
	int son[26] = {0};
	int num;
	int last;
};
struct node t[N * 30 + 5];
int cnt;
int get_place(int place,int x)
{
	if(!t[place].son[x])
		t[place].son[x] = ++cnt;
	return t[place].son[x];
}
char a[N + 5], b[35];
int main()
{
	int T, n;
	scanf("%d", &T);
	while(T--)
	{
		cnt = 1;
		memset(t, 0, sizeof(t));
		scanf("%s", a + 1);
		int len = strlen(a + 1);
		for (int i = 1; i <= len;i++)
		{
			int place = 1;
			for (int j = 1; j <= min(30, len - i + 1); j++)
			{
				place = get_place(place, a[i + j - 1] - 97);
				if(t[place].last<=i)
				{
					t[place].last = i + j;//下一次至少得从i+j位(当前位为i+j-1)开始计算
					t[place].num++;
				}
			}
		}
		scanf("%d", &n);
		while(n--)
		{
			scanf("%s", b + 1);
			int place = 1, l = strlen(b + 1);
			for (int i = 1; i <= l;i++)
				place = get_place(place, b[i] - 97);
			printf("%d\n", t[place].num);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值