2021牛客暑期多校训练营2

B.Cannon

题意

有一个 2 × 1 0 100 2\times 10^{100} 2×10100的棋盘,第一行摆了 n n n个炮,第二行摆了 m m m个炮。一个炮吃掉另一个炮当且仅当中间只有一个炮。设 f k f_k fk为k个炮吃炮事件的方案数,在两种情况下,第一种是两行可以交替发生事件;第二种是必须第一行发生完第二行才能发生,求方案的异或和。

思路

在有n个炮的一行中吃一次的方案数为 2 × ( n − 2 ) 2\times(n-2) 2×(n2)
在有n个炮的一行中吃m次的方案数为 2 m × ( n − 2 ) × ( n − 2 − 1 ) × ⋅ ⋅ ⋅ × ( n − 2 − m − 1 ) = 2 m × ( n − 2 ) ! ( n − 2 − m ) ! 2^m\times(n-2)\times(n-2-1)\times···\times(n-2-m-1)=2^m\times\frac{(n-2)!}{(n-2-m)!} 2m×(n2)×(n21)××(n2m1)=2m×(n2m)!(n2)!
第一行有n个炮,第二行有m个炮,设 n = n − 2 , m = m − 2 n=n-2,m=m-2 n=n2,m=m2,方便计算
第一种情况的方案数为 ∑ k = 0 m + n 2 k ∑ i = 0 k C k i n ! ( n − i ) ! m ! ( m − ( k − i ) ) ! \sum_{k=0}^{m+n}2^k\sum_{i=0}^{k}C_k^i\frac{n!}{(n-i)!}\frac{m!}{(m-(k-i))!} k=0m+n2ki=0kCki(ni)!n!(m(ki))!m!
意义为,枚举每个k,对于当前k来说,第一次发生i次,第二行发生k-i次,然后第一种情况可以两行交替进行,k个位置选i个给第一行,剩下的为第二行。
分析 ∑ i = 0 k C k i n ! ( n − i ) ! m ! ( m − ( k − i ) ) ! \sum_{i=0}^{k}C_k^i\frac{n!}{(n-i)!}\frac{m!}{(m-(k-i))!} i=0kCki(ni)!n!(m(ki))!m!
= ∑ i = 0 k k ! i ! ( k − i ) ! n ! ( n − i ) ! m ! ( m − ( k − i ) ) ! =\sum_{i=0}^{k}\frac{k!}{i!(k-i)!}\frac{n!}{(n-i)!}\frac{m!}{(m-(k-i))!} =i=0ki!(ki)!k!(ni)!n!(m(ki))!m!
= ∑ i = 0 k k ! n ! i ! ( n − i ) ! m ! ( k − i ) ! ( m − ( k − i ) ) ! =\sum_{i=0}^{k}k!\frac{n!}{i!(n-i)!}\frac{m!}{(k-i)!(m-(k-i))!} =i=0kk!i!(ni)!n!(ki)!(m(ki))!m!
= k ! ∑ i = 0 k C n i C m k − i =k!\sum_{i=0}^kC_n^iC_m^{k-i} =k!i=0kCniCmki
= k ! C m + n k =k!C_{m+n}^{k} =k!Cm+nk
故第一种情况的方案数为 ∑ k = 0 m + n 2 k k ! C m + n k \sum_{k=0}^{m+n}2^kk!C_{m+n}^k k=0m+n2kk!Cm+nk
然后考虑第二种情况的方案数 ∑ k = 0 m + n 2 k ∑ i = 0 k n ! ( n − i ) ! m ! ( m − ( k − i ) ) ! \sum_{k=0}^{m+n}2^k\sum_{i=0}^k\frac{n!}{(n-i)!}\frac{m!}{(m-(k-i))!} k=0m+n2ki=0k(ni)!n!(m(ki))!m!
这一步不难理解,那么分析 ∑ i = 0 k n ! ( n − i ) ! m ! ( m − ( k − i ) ) ! \sum_{i=0}^k\frac{n!}{(n-i)!}\frac{m!}{(m-(k-i))!} i=0k(ni)!n!(m(ki))!m!
分子分母同时乘 ( m + n − k ) ! (m+n-k)! (m+nk)!,变为 ∑ i = 0 k n ! m ! ( m + n − k ) ! ( m + n − k ) ! ( n − i ) ! ( m − ( k − i ) ) ! \sum_{i=0}^k\frac{n!m!}{(m+n-k)!}\frac{(m+n-k)!}{(n-i)!(m-(k-i))!} i=0k(m+nk)!n!m!(ni)!(m(ki))!(m+nk)!
= n ! m ! ( m + n − k ) ! ∑ i = 0 k C m + n − k n − i =\frac{n!m!}{(m+n-k)!}\sum_{i=0}^kC_{m+n-k}^{n-i} =(m+nk)!n!m!i=0kCm+nkni
分析 ∑ i = 0 k C m + n − k n − i = ∑ i = n − k n C m + n − k i = ∑ i = 0 n C m + n − k i − ∑ i = 0 n − k − 1 C m + n − k i \sum_{i=0}^kC_{m+n-k}^{n-i}=\sum_{i=n-k}^{n}C_{m+n-k}^i=\sum_{i=0}^{n}C_{m+n-k}^i-\sum_{i=0}^{n-k-1}C_{m+n-k}^i i=0kCm+nkni=i=nknCm+nki=i=0nCm+nkii=0nk1Cm+nki
我们设 S ( n , m ) = ∑ i = 0 m C n i S(n,m)=\sum_{i=0}^mC_n^i S(n,m)=i=0mCni
则为 S ( m + n − k , n ) − S ( m + n − k , n − k − 1 ) S(m+n-k,n)-S(m+n-k,n-k-1) S(m+nk,n)S(m+nk,nk1)
就可以得到前缀和 S ( n , m + 1 ) = S ( n , m ) + C n m + 1 S(n,m+1)=S(n,m)+C_n^{m+1} S(n,m+1)=S(n,m)+Cnm+1
S ( n , m − 1 ) = S ( n , m ) − C n m S(n,m-1)=S(n,m)-C_n^m S(n,m1)=S(n,m)Cnm
S ( n + 1 , m ) = ∑ i = 0 m C n + 1 i = ∑ i = 0 m C n i + C n i − 1 = 2 S ( n , m ) − C n m S(n+1,m)=\sum_{i=0}^mC_{n+1}^i=\sum_{i=0}^mC_n^i+C_n^{i-1}=2S(n,m)-C_n^m S(n+1,m)=i=0mCn+1i=i=0mCni+Cni1=2S(n,m)Cnm
S ( n + 1 , m + 1 ) = 2 S ( n , m + 1 ) − C n m + 1 = 2 S ( n , m ) + C n m + 1 S(n+1,m+1)=2S(n,m+1)-C_n^{m+1}=2S(n,m)+C_n^{m+1} S(n+1,m+1)=2S(n,m+1)Cnm+1=2S(n,m)+Cnm+1
然后对于这两个前缀和分别求解到两个数组里,做差就是答案。
故第二种情况的方案数为 ∑ k = 0 m + n 2 k n ! m ! ( n = m − k ) ! s k \sum_{k=0}^{m+n}2^k\frac{n!m!}{(n=m-k)!}s_k k=0m+n2k(n=mk)!n!m!sk

代码

/*
 * @Author: Icey_dying
 * @Date: 2021-08-26 16:35:31
 * @LastEditors: Icey_dying
 * @LastEditTime: 2021-08-26 18:13:26
 * @FilePath: \Icey_dying\competition\2021\2021.07\2021.07.19\B.cpp
 */
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e9+9;
const int MAXN=1e7+9;
void read(int &x){
    int ret=0;
    char c=getchar(),last;
    while(!isdigit(c)) {last=c;c=getchar();}
    while(isdigit(c)) {ret=ret*10+c^48;c=getchar();}
    x=last=='-'?-ret:ret; 
}
ll quickmod(ll a,ll b){
    ll ret=1;
    while(b){
        if(b&1) ret=ret*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ret;
}
int Fac[MAXN],Inv[MAXN],qpow2[MAXN];
int f1[MAXN],f2[MAXN],s1[MAXN],s2[MAXN];
//求组合数的模板,只求组合数的话qmod2没用,只需要Fac和Inv即可
void Prepare(int n)
{
    Fac[0] = 1; qpow2[0] = 1;
    for (int i = 1; i <= n; i ++)
        qpow2[i] = 2ll * qpow2[i - 1] % mod;
    for (int i = 1; i <= n; i ++)
        Fac[i] = 1ll * Fac[i - 1] * i % mod;
    Inv[0] = Inv[1] = 1;
    for (int i = 2; i <= n; i ++)
        Inv[i] = (ll)(mod - mod / i) * Inv[mod % i] % mod;
    for (int i = 2; i <= n; i ++)
        Inv[i] = 1ll * Inv[i - 1] * Inv[i] % mod;
}
inline int C(int u, int v)
{
    if (u < 0 || v < 0 || u < v) return 0;
    return 1ll * Fac[u] * Inv[v] % mod * Inv[u - v] % mod;
}
//模板结束
int n,m;
void solve(){
    s1[m+n]=1;
    for(int i=m+n;i>=1;i--)
        s1[i-1]=(2ll*s1[i]%mod-1ll*C(n+m-i,n)%mod+mod)%mod;
    s2[n-1]=1;
    for(int i=n-1;i>=1;i--)
        s2[i-1]=(2ll*s2[i]%mod+1ll*C(n+m-i,n-i)%mod)%mod;
    for(int i=m+n;i>=0;i--)
        s1[i]=(s1[i]-s2[i]+mod)%mod;
    ll ans1=0,ans2=0;
    for(int i=0;i<=n+m;i++){
        f1[i]=1ll*qpow2[i]*Fac[m+n]%mod*Inv[m+n-i]%mod;
        f2[i]=1ll*qpow2[i]*Fac[n]%mod*Fac[m]%mod*Inv[n+m-i]%mod*s1[i]%mod;
    }
    for(int i=0;i<=n+m;i++){
        ans1^=f1[i];
        ans2^=f2[i];
    }
    printf("%lld %lld\n",ans1,ans2);
}
int main()
{
    scanf("%d%d",&n,&m);
    n-=2; m-=2;
    Prepare(m+n);
    solve();
    return 0;
}

C.Draw Grids

题意

给定一个n*m的点阵,每次选两个相邻的点进行连线
两个人轮流操作,要求不能连出封闭图形。不能连线的人输。

思路

我们考虑两个人最多能连多少根线,肯定先画不互相垂直的线,也就是先画所有的横着或者竖着的线(哪个多画哪个)。画完之后,我们只能再画反方向(指的是另一个方向)的了。那么这时候显然只能画一溜,这样的话就是 m i n ( m , n ) ∗ [ m a x ( m , n ) − 1 ] + m i n ( m , n ) − 1 = m i n ( m , n ) ∗ m a x ( m , n ) − 1 = m ∗ n − 1 min(m,n) *[ max(m,n)-1]+min(m,n)-1=min(m,n)*max(m,n)-1=m*n-1 min(m,n)[max(m,n)1]+min(m,n)1=min(m,n)max(m,n)1=mn1根线,那么我们最后两个人一共画了 n ∗ m − 1 n * m - 1 nm1的线。那么我们只需要判断这个数的奇偶即可。

代码

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

int main()
{
    int n,m;
    cin>>n>>m;
    if(!(n%2)) cout<<"YES\n";
    else if(!(m%2)) cout<<"YES\n";
    else cout<<"NO\n";
    return 0;
}

K.Stack

题意

题目告诉你利用单调栈几个点前面比它小并包含它自身的元素有多少个
让你构造一个包含1到n并每个数字只出现一次的序列

思路

题目所给的数组b是不全的,那么我们就将b补全并构造符合题意的序列
对于没有给出的 b i b_i bi,可以通过 b i − 1 b_{i-1} bi1来补,即 b i = b i − 1 + 1 b_i=b_{i-1}+1 bi=bi1+1
ps. b 1 b_1 b1如果没给的话,默认为1
而对于给出的 b i b_i bi,首先判断其合不合法,即 b i ≤ i b_i\le i bii
然后比较相邻两个之差,即 b i ≤ b i − 1 + 1 b_i\le b_{i-1}+1 bibi1+1
将数组b补全之后,我们可以从后往前遍历
通过栈中元素的数量一次把1,2,3…按递增顺序放入栈中
栈中元素的数量表示的是递增序列的长度,那么当栈中数量达到序列的长度即加入数字后等于栈中元素数量的时候,就直接把数字当成该点的值即可

代码

#include <bits/stdc++.h>
using namespace std;
int b[1000010];
int a[1000010];
int f[1000010];
int main()
{
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=k;i++){
        int x;
        cin>>x>>a[x];
    }
    for(int i=1;i<=n;i++){
        if(!a[i]) a[i]=a[i-1]+1;
        else if(a[i]>a[i-1]+1){//不需要比较bi和i,这个条件更强
            cout<<"-1"<<endl;
            return 0;
        }
    }
    int cnt=0,top=0;
    for(int i=n;i>=1;i--){
        while(a[i]>top) f[++top]=++cnt;++cnt能保证先把小的弹出来再把大的弹出来
        b[i]=f[top];
        top--;
    }
    for(int i=1;i<=n;i++) cout<<b[i]<<' ';
    return 0;
}

L.WeChat Walk

题意

给出 n n n个人,再给出 m m m对好友关系,每个人都有一个朋友圈用来显示微信步数。
朋友关系不具备传递性
现在有 q q q次操作,每次操作会让某个人的微信步数增加,问最后对于每个人来说,在自己朋友圈内获得冠军的时间

思路

因为权值在 [ 0 , 10000 ] [0,10000] [0,10000]的范围内,直接倒序枚举权值
设点 u u u在第 t i m tim tim秒达到权值 v a l val val,并在此权值下维持了 z z z秒冠军
那么 z = max ⁡ ( 0 , min ⁡ ( l a s u , min ⁡ ( u − > v ) { f v } ) − t i m ) z=\max (0,\min (las_u,\min \limits_{(u->v)}\{f_v\})-tim) z=max(0,min(lasu,(u>v)min{fv})tim)
其中 l a s u las_u lasu表示点 u u u上一次以更高权值(或等于)出现的时间, f v f_v fv表示点 v v v最近一次以更高权值(或等于)出现的时间
显然,如果倒序枚举 w ∈ [ 10000 , 0 ] w\in[10000,0] w[10000,0],然后对 q q q个询问依次更新
对于一个点,我们总是需要去遍历所有相邻的点 v v v计算 min ⁡ { f v } \min\{f_v\} min{fv}
这样复杂度会被菊花图卡死.考虑分块
若一个点周围的边小于等于 m \sqrt{m} m 叫做小点,显然可以暴力计算
若一个点周围边数大于等于 m \sqrt{m} m 叫做大点,继续暴力遍历周围的点计算复杂度显然太高
那么可以考虑让大点"被"更新.也就是对每个大点动态维护一个 min ⁡ { f v } \min\{f_v\} min{fv}
在那些小点被更新的时候,顺便更新一下小点周围的大点的 min ⁡ { f v } \min\{f_v\} min{fv}

因为一个小点周围的大点数不会超过 m m \frac{m}{\sqrt{m}} m m个,所以这样总体复杂度就是 O ( q m ) O(q\sqrt{m}) O(qm )

代码

/*
 * @Author: Icey_dying
 * @Date: 2021-08-26 18:22:36
 * @LastEditors: Icey_dying
 * @LastEditTime: 2021-08-26 18:35:21
 * @FilePath: \Icey_dying\competition\2021\2021.07\2021.07.19\L.cpp
 */
#include <bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,m,q;
int r[MAXN],f[MAXN],las[MAXN],ans[MAXN],mi[MAXN],w[MAXN];
vector<int> vec[MAXN],bi[MAXN];
vector<pair<int,int> >t[MAXN];
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    int square=sqrt(m);
    for(int i=1,l,r;i<=m;i++){
        scanf("%d%d",&l,&r);
        vec[l].push_back(r);
        vec[r].push_back(l);
    }
    for(int i=1;i<=n;i++){
        if(vec[i].size()<square) continue;
        for(auto v:vec[i]) bi[v].push_back(i);//记录大点
    }
    memset(w,0,sizeof(w));
    for(int i=1,x,y;i<=q;i++){
        scanf("%d%d",&x,&y);
        w[x]+=y;
        t[w[x]].push_back({x,i});//第i个时间点x步数更新为w[x]
    }
    for(int i=1;i<=n;i++) f[i]=las[i]=mi[i]=q;
    for(int i=10000;i>=1;i--){
        for(auto it:t[i]){
            int u=it.first,tim=it.second;
            for(auto v:bi[u])
                mi[v]=min(mi[v],tim);
            las[u]=f[u];
            f[u]=tim;
        }
        for(auto it:t[i]){
            int u=it.first,tim=it.second;
            if(vec[u].size()<square){
                int r=las[u];
                for(auto v:vec[u]) r=min(r,f[v]);
                ans[u]+=max(0,r-tim);
            }
            else ans[u]+=max(0,min(mi[u],las[u])-tim);
        }
    }
    for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值