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×(n−2)
在有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×(n−2)×(n−2−1)×⋅⋅⋅×(n−2−m−1)=2m×(n−2−m)!(n−2)!
第一行有n个炮,第二行有m个炮,设
n
=
n
−
2
,
m
=
m
−
2
n=n-2,m=m-2
n=n−2,m=m−2,方便计算
第一种情况的方案数为
∑
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+n2k∑i=0kCki(n−i)!n!(m−(k−i))!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(n−i)!n!(m−(k−i))!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!(k−i)!k!(n−i)!n!(m−(k−i))!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!(n−i)!n!(k−i)!(m−(k−i))!m!
=
k
!
∑
i
=
0
k
C
n
i
C
m
k
−
i
=k!\sum_{i=0}^kC_n^iC_m^{k-i}
=k!∑i=0kCniCmk−i
=
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+n2k∑i=0k(n−i)!n!(m−(k−i))!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(n−i)!n!(m−(k−i))!m!
分子分母同时乘
(
m
+
n
−
k
)
!
(m+n-k)!
(m+n−k)!,变为
∑
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+n−k)!n!m!(n−i)!(m−(k−i))!(m+n−k)!
=
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+n−k)!n!m!∑i=0kCm+n−kn−i
分析
∑
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+n−kn−i=∑i=n−knCm+n−ki=∑i=0nCm+n−ki−∑i=0n−k−1Cm+n−ki
我们设
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+n−k,n)−S(m+n−k,n−k−1)
就可以得到前缀和
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,m−1)=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+Cni−1=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=m−k)!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=m∗n−1根线,那么我们最后两个人一共画了 n ∗ m − 1 n * m - 1 n∗m−1的线。那么我们只需要判断这个数的奇偶即可。
代码
#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}
bi−1来补,即
b
i
=
b
i
−
1
+
1
b_i=b_{i-1}+1
bi=bi−1+1
ps.
b
1
b_1
b1如果没给的话,默认为1
而对于给出的
b
i
b_i
bi,首先判断其合不合法,即
b
i
≤
i
b_i\le i
bi≤i
然后比较相邻两个之差,即
b
i
≤
b
i
−
1
+
1
b_i\le b_{i-1}+1
bi≤bi−1+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}} mm个,所以这样总体复杂度就是 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;
}