Loading

2021牛客OI赛前集训营-提高组(第二场)

T1 串串串

题目描述

题面

你有两个长度为 \(n, m\)\(01\)\(S, T\)

\(Q\) 次询问,每次询问给出 \(l_1, r_1, l_2, r_2\),其中 \(r_1 - l_1 + 1 = r_2 - l_2 + 1\)\(a = S[l_1 \dots r_1]\), \(b = T[l_2 \dots r_2]\),你需要求出 \(a\neq b\) 的位置个数对 \(2\) 取模。

\(n, m, q \leq 2\times 10^5\)

solution

本题的点在对 \(2\) 取模上。

\(30\) 分暴力就不说了。

\(100\)

算法一:

可以发现,交换 \(a\) 中任意两个位置,答案是不变的,交换 \(b\) 中任意两个位置也一样。

那么我们可以将 \(a, b\) 中得 \(0\) 放在前面,\(1\) 放在后面,答案即为 \(a\)\(1\) 的个数和 \(b\)\(1\) 的个数之差对 \(2\) 取模的结果。

时间复杂度 \(O(n + m + 1)\)

std

#include<iostream>
#include<cstdio>
#include<cassert>
using namespace std;
const int N=200005;
int n,m,Q;
char s[N],t[N];
int a[N],b[N];
int main()
{
    scanf("%d%d",&n,&m);
    scanf("%s",s+1), scanf("%s",t+1);
    for(int i=1;i<=n;i++) a[i]=a[i-1]+(s[i]=='1');
    for(int i=1;i<=m;i++) b[i]=b[i-1]+(t[i]=='1');
    scanf("%d",&Q);
    while(Q--)
    {
        int l1,r1,l2,r2;
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        if(((a[r1]-a[l1-1])%2)==((b[r2]-b[l2-1])%2)) printf("0\n");
        else printf("1\n");
    }
    return 0;
}

算法二

不难发现答案就是 \(a\)\(b\) 异或起来 \(1\) 的个数,如果为奇数就为 \(1\), 为偶数就为 \(0\)

发现奇数个 \(1\) 异或起来正好等于 \(1\),偶数个异或起来等于 \(0\),所以直接两个区间的异或值异或起来就是答案。

复杂度 \(O(m + n)\)

code

/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define ll long long
#define rg register
using namespace std;
const int MAXN = 2e5 + 5;
int read(){
    int x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
int preb[MAXN], prea[MAXN], n, m, a[MAXN], b[MAXN], Q;
char s[MAXN], t[MAXN];
int main(){
   n = read(), m = read();
   scanf("%s%s", s + 1, t + 1);
   for (int i = 1; i <= n; i++) a[i] = s[i] - '0', prea[i] = prea[i - 1] ^ a[i];
   for (int i = 1; i <= m; i++) b[i] = t[i] - '0', preb[i] = preb[i - 1] ^ b[i];
   Q = read();
   while(Q--) {
   	 int l = read(), r = read(), L = read(), R = read();
   	 int ret = prea[r] ^ prea[l - 1], res = preb[R] ^ preb[L - 1];
   	 int Ans = ret ^ res;
   	 cout<<Ans<<"\n";
   }
   puts("");
   return 0;
}

T2 方格计数

题目描述

在左下角是 \((0,0)\),右上角是 \((W,H)\) 网格上,有 \((W+1)\times (H+1)\) 个格点。

现在要在格点上找 \(N\) 个不同的点,使得这些点在一条直线上。并且在这条直线上,相邻点之间的距离不小于 \(D\)。求方案数模 \(10^9+7\)

题面

\(1 \leq N \leq 50, W, H, D \leq 500, T \leq 20\)

solution

求方案数一开始想的是枚举直线,然后找出直线上的所有的点,然后 \(dp\) 求方案。但是现实是我不会枚举直线,暴力枚举直线上的点会炸掉,然后我就爆 \(0\) 了。

正解

知识点:组合数

网格图上,一条直线上的两个点,\(gcd(|x_1 - x_2|, |y_1, y_2|) - 1\) 就是直线上两点间点的数目。

证明

任意两个元素间隔大于等于 \(k\) 的组合数,在 \(A\) 中选 \(B\) 个位置,要求每相邻位置要隔出至少 \(C\) 个空位置的方案。

相邻的有 \(B-1\) 对,所以空 \((B-1)C\) 个,去掉这些位置就变成普通的选位置了,答案就是 \(C(A-(B-1)C,B)\)

30pts

虑枚举两个端点,强制两个端点选,令 \(a\) 为两个端点之间 \(x\) 轴上的距离,\(b\) 为两个端点 \(y\) 轴上的距离,那么这里面可以选择的点的个数有 \(g=\gcd(a,b)\) 个。我们要求 \(N-2\) 个小球(强制两个端点选),需要放到 \(g\) 个盒子里,相邻两个小球的盒子编号差至少为 \(k\),方案数为

\[{N - 2 \choose {g + 1 - 2k - (N - 3)(k - 1)}} \]

复杂度 \(O(TW^2H^2)\)

100pts

延续 \(30\) 分的思路,发现对于相同的 \(a,b\) 方案数也是相同的,考虑枚举 \(a,b\)\(30\) 分一样做,最后再乘个 \((W-a+1)\times (H-b+1)\) 就好了,时间复杂度 \(O(TWH)\)

复杂度 \(O(TWH)\)

code

/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 2000 + 5;
const int mod = 1e9 + 7;
int read() {
  int x = 0, f = 1; char c = getchar();
  while(c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}
  while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
  return x * f;
}
int T, n, w, h, d, C[MAXN][MAXN];
double dis(int a, int b, int x, int y) {
  return sqrt((y - b) * (y - b) + (x - a) * (x - a));
}
int gcd(int a, int b){return b == 0 ? a:gcd(b, a % b);}
void Pre(){
   C[0][0] = 1;
   for (int i = 1; i <= 2000; i++) {
    C[i][0] = 1;
    for (int j = 1; j <= i; j++) {
      C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
    }
   }
} 
long long work(int a, int b){
   if(a == 0 && b == 0) return 0;
   int g = gcd(a, b);
   int k = ceil(d / dis(0, 0, a / g, b / g));//相邻连的两点的编号差
   if(k * (n - 1) > g) return 0;
   int res = C[g + 1 - 2 * k - (k - 1) * (n - 3)][n - 2];
   if(a != 0 && b != 0) res = (res + res) % mod;
   res = (long long)res *(w - a + 1) * (h - b + 1) % mod;
   return res;
}
int main() {
   T =  read();
   Pre();
   while(T--) {
     n = read(), w = read(), h = read(), d = read();
     if(n == 1) {
       printf("%d\n", (w + 1) * (h + 1));
       continue;
     }
     int Ans = 0;
     for (int i = 0; i <= w; i++) 
       for (int j = 0; j <= h; j++) Ans = (Ans + work(i, j)) % mod;
     printf("%d\n", Ans);
   }  
   system("pause");
   return 0;
}

T3 树数树

题目描述

牛牛有一棵 \(n\) 个点的有根树,根为 \(1\)
我们称一个长度为 \(m\) 的序列 \(a\) 是好的,当且仅当:

  • \(\forall i \in (1, m], a_i\)\(a_{i - 1}\) 的祖先或 \(a_{i - 1}\)\(a_i\) 的祖先;

  • \(\forall 1 \leq i < j \leq m, a_i \neq a_j\)

\(n \leq 10^5\)

题面

solution

错把祖先当作父亲,自裁 /kk

算法一:

\(f_{u, i}\) 表示 \(u\)\(u\) 的子树中,允许使用子树外 \(i\) 个祖先所得到的最长上升长度是多少,转移相当于各个儿子的一个\(\max\) 卷积。

时间复杂度:\(O(n^2)\) 期望得分 \(30\)

算法二:

可以发现,一个节点 \(u\) 可以将子树中的两个序列拼成一个序列,且我们在处理完 \(u\) 的父亲的时候 \(u\) 的状态已经不用管了,我们可以用堆维护出 \(u\)\(u\) 的子树中的点能组成的序列,转移相当于是将所有子树的堆合并成一个,然后取出其中最大的两个合并成一个。

可以用启发式合并或者可并堆维护这个过程。

时间复杂度 \(O(nlog^2n) \sim O(nlogn)\)

下面是启发式合并的代码:

/*
work by:Ariel_
Sorce:
Knowledge:启发式合并
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
const int MAXN = 2e5 + 5;

int read() {
  int x = 0, f = 1; char c = getchar();
  while(c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}
  while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
  return x * f;
}
int T, n, id[MAXN];
struct edge{int nxt, v;}e[MAXN << 1];
int E, head[MAXN];
void add_edge(int u, int v) {
  e[++E] = (edge){head[u], v};
  head[u] = E;
}
void Clear() {
   memset(head, 0, sizeof head);
   E = 0;
}
priority_queue<int> q[MAXN];
int Merge(int x, int y) {
   if(q[x].size() > q[y].size()) swap(x, y);
   while(!q[x].empty()) {
     int u = q[x].top(); q[x].pop();
     q[y].push(u);
   }
   return y;
}
void dfs(int x, int fa) {
  id[x] = x;
  while(!q[id[x]].empty())q[id[x]].pop();
  for(int i = head[x]; i; i = e[i].nxt) {
       int v = e[i].v;
       if(v == fa) continue;
       dfs(v, x);
       id[x] = Merge(id[x], id[v]);
  }
  if(q[id[x]].empty()) q[id[x]].push(1);
  else {
    int w = q[id[x]].top(); q[id[x]].pop();
    if(!q[id[x]].empty()) w += q[id[x]].top(), q[id[x]].pop();
    q[id[x]].push(w + 1);
  }
}
int main(){
   T = read();
   while(T--) {
     n = read();
     Clear();
     for (int i = 1; i < n; i++) {
        int u = read(), v = read();
        add_edge(u, v), add_edge(v, u);
     }
     dfs(1, 0);
     printf("%d\n", q[id[1]].top());
   }
   system("pause");
   return 0;
}

T4 序列

题面

定义一个数的 se 序列为其一个数位和为 \(10\) 的子段。

举个例子,1145141919810900 的所有 se 序列为

  • 145
  • 451
  • 514
  • 19
  • 91
  • 19
  • 109
  • 1090
  • 10900

定义一个数是 ll 数,当且仅当它的每一个数位都在至少一个 se 序列中。

举个例子,1145141919810900 不是 ll 数,因为第一个 1 和 8 不在任何一个 se 序列中,而 23541901 是一个 ll 数。

现在牛牛想随机生成一个 \([0,10^n)\) 范围内的数送给牛妹。具体地说,每一位上的数字为 \(i\) 的概率为 \(a_i\), 且保证 \(\sum\limits_{i=0}^9 a_i=1\)
现在牛牛想知道这个数为 ll 数的概率。

数据范围

对于 \(5\%\) 的数据,\(n=1\)

对于 \(5\%\) 的数据,\(n=100\)

对于 \(20\%\) 的数据,\(n=3000\)

对于另 \(30\%\) 的数据,\(n\le 10^{18}\),且保证 \(\forall i\in [0,9],a_i=\frac{1}{10}\)

对于 \(100\%\) 的数据,\(1\le n\le 10^{18}\), \(\forall i \in [0,9],0\le b_i\lt 10^9+7\)

solution

正解:特征多项式。

直接弃疗。

std

#include<iostream>
#include<cstdio>
#include<cassert>
#include<cstring>
#include<vector>
#include<map>
using namespace std;
const int N=100005;
const int MOD=1000000007;
int ksm(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1) res=1LL*res*a%MOD;
        a=1LL*a*a%MOD,b>>=1;
    }
    return res;
}
int getinv(int x)
{
    return ksm(x,MOD-2);
}
int n=2816*2;
int cc[10];
int a[N];
vector<int>solve()
{
    static vector<int>R[N];
    static int c;
    static int delta[N];
    static int fail[N];
    for(int i=1;i<=n;i++)
    {
        delta[c]=a[i];
        for(int j=0;j<(int)R[c].size();j++)
            delta[c]=(delta[c]-(long long)a[i-j-1]*R[c][j]%MOD+MOD)%MOD;
        if(delta[c]==0) continue;
        fail[c]=i;
        if(c==0)
        {
            R[++c]=vector<int>(i,0);
            continue;
        }
        int d=0;
        for(int j=1;j<c;j++)
            if((int)R[j].size()-fail[j]<(int)R[d].size()-fail[d]) d=j;
        int qwq=(long long)delta[c]*getinv(delta[d])%MOD;
        R[++c]=vector<int>(i-fail[d]-1,0);
        R[c].emplace_back(qwq);
        for(auto j:R[d])
            R[c].emplace_back((MOD-(long long)qwq*j%MOD)%MOD);
        if(R[c].size()<R[c-1].size()) R[c].resize(R[c-1].size());
        for(int j=0;j<(int)R[c-1].size();j++)
            R[c][j]=(R[c][j]+R[c-1][j])%MOD;
    }
    return R[c];
}
int k;
long long val[N];
int calc_f(long long n)
{
    static int re[N];
    static int re_top;
    static int x[N];
    static int x_top;
    static int tp[N];
    static int tp_top;
    if(n<=k) return a[n];
    re[re_top=0]=1;
    x[x_top=1]=1;
    n-=k;
    for(;n;n>>=1)
    {
        if(n&1)
        {
            tp_top=re_top;
            for(int i=0;i<=re_top+x_top;i++)
                tp[i]=re[i],re[i]=0;
            re_top=tp_top+x_top;
            for(int i=0;i<=tp_top;i++)
                for(int j=0;j<=x_top;j++)
                    re[i+j]=(re[i+j]+(long long)tp[i]*x[j])%MOD;
            //times
            while(re_top>=k)
            {
                for(int i=0;i<k;i++)
                    re[re_top-k+i]=(re[re_top-k+i]+(long long)val[k-i]*re[re_top])%MOD;
                re[re_top]=0;
                while(re[re_top]==0) re_top--;
            }
            //mod
        }
        tp_top=x_top;
        for(int i=0;i<=tp_top*2;i++)
            tp[i]=x[i],x[i]=0;
        x_top=tp_top+tp_top;
        for(int i=0;i<=tp_top;i++)
            for(int j=0;j<=tp_top;j++)
                x[i+j]=(x[i+j]+(long long)tp[i]*tp[j])%MOD;
        //times
        while(x_top>=k)
        {
            for(int i=0;i<k;i++)
                x[x_top-k+i]=(x[x_top-k+i]+(long long)val[k-i]*x[x_top])%MOD;
            x[x_top]=0;
            while(x[x_top]==0) x_top--;
        }
        //mod
    }
    int ans=0;
    for(int i=0;i<=re_top;i++)
        ans=(ans+(long long)re[i]*a[k+i])%MOD;
    return ans;
}
int tot;
map<pair<long long,int>,int>book;
void dfs(int les,long long val)
{
    if(les==0)return;
    long long tp=val,q=0;
    book[{val,0}]=++tot;
    while(tp)
    {
        q++;
        book[{val,q}]=++tot;
        tp/=10;
    }
    for(int i=1;i<=min(les,9);i++)
        dfs(les-i,val*10+i);
    return;
}
vector<pair<int,int>>tr[2817];
void solve(long long sta,int les)
{
    int qwq=book[{sta,les}];
    tr[qwq].emplace_back(qwq,cc[0]);
    for(int i=1;i<=9;i++)
    {
        long long tp=sta,sum=0,id=-1;
        for(int j=0;j<=8;j++)
        {
            if(tp%10+sum+i>=10)
            {
                id=j;
                break;
            }
            sum+=tp%10,tp/=10;
        }
        if(id==-1)tr[qwq].emplace_back(book[{sta*10+i,les+1}],cc[i]);
        else
        {
            if(id+1<les) continue;
            long long nw=1;
            for(int j=1;j<=id;j++)
                nw*=10;
            if(tp%10+sum+i==10) tr[qwq].emplace_back(book[{sta%nw*10+i,0}],cc[i]);
            else if(id>=les) tr[qwq].emplace_back(book[{sta%nw*10+i,les+1}],cc[i]);
        }
    }
    return;
}
int main()
{
    long long m;
    assert(scanf("%lld",&m)==1);
    assert(m<=1e18);
    static int b[10];
    for(int i=0;i<=9;i++)
        assert(scanf("%d",&b[i])==1),assert(0<=b[i]&&b[i]<MOD);
    long long sum=0;
    for(int i=0;i<=9;i++)
        sum=(sum+b[i])%MOD;
    for(int i=0;i<=9;i++)
        cc[i]=(long long)b[i]*getinv(sum)%MOD;
    dfs(10,0);
    static int ans[2817];
    for(auto [vq,id]:book)
        if(vq.second==0) ans[id]=1;
    for(auto [vq,id]:book)
        solve(vq.first,vq.second);
    for(int i=1;i<=n;i++)
    {
        static int tt[2817];
        for(int j=1;j<=2816;j++)
            for(auto [v,c]:tr[j])
                tt[j]=(tt[j]+(long long)ans[v]*c)%MOD;
        a[i]=tt[1];
        for(int j=1;j<=2816;j++)
            ans[j]=tt[j],tt[j]=0;
    }
    vector<int>ret=solve();
    k=ret.size();
    for(int i=1;i<=k;i++)
        val[i]=ret[i-1];
    int Zero=ksm(cc[0],m%(MOD-1));
    int res=(calc_f(m)-Zero+MOD)%MOD;
    printf("%d",res);
    return 0;
}
posted @ 2021-10-07 21:44  Dita  阅读(264)  评论(2编辑  收藏  举报