D. Garbage Removal
题意
有一个 H × W H \times W H×W 的网格, ( i , j ) (i,j) (i,j) 表示第 i i i 行第 j j j 列的格子。在这个网格里有 N N N 个垃圾,第 i i i 个垃圾在 ( X i , Y i ) (X_i,Y_i) (Xi,Yi)。
有 Q Q Q 次查询,分为以下两种类型:
1 x
输出第 x x x 行垃圾的总数,并移除这行所有垃圾;2 y
输出第 y y y 列垃圾的总数,并移除这列所有垃圾。
思路
由于删除一行垃圾时,对应列的垃圾数也会减少。所以考虑用 set \text{set} set 维护每行、每列的状态。
定义 set<int> X[]
(大小为
H
H
H),
X
i
X_i
Xi 表示第
i
i
i 行有垃圾的所有列号的集合;和 set<int> Y[]
(大小为
W
W
W),
Y
i
Y_i
Yi 表示第
i
i
i 列有垃圾的所有行号的集合。
初始化将所有垃圾都加入对应集合,注意 X X X 和 Y Y Y 都要加。
若为操作 1 1 1,输出集合 X x X_x Xx 的大小,并且,对于其中每一个数 i i i(列号),删除对应集合 Y i Y_i Yi 中的 x x x 元素,并清空集合 X x X_x Xx;
若为操作 2 2 2,输出集合 Y y Y_y Yy 的大小,并且,对于其中每一个数 i i i(行号),删除对应集合 X i X_i Xi 中的 y y y 元素,并清空集合 Y y Y_y Yy。
时间复杂度: O ( Q + N ) O(Q+N) O(Q+N)
C++ 代码
#include<bits/stdc++.h>
#define sz(v) (int)v.size()
using namespace std;
const int maxn=200005;
int n,m,k;
set<int> x[maxn];
set<int> y[maxn];
int main(){
cin>>n>>m>>k;
for(int i=1;i<=k;i++){
int u,v;
cin>>u>>v;
x[u].insert(v);
y[v].insert(u);
}
int Q;
cin>>Q;
while(Q--){
int opt,p;
cin>>opt>>p;
if(opt==1){
cout<<sz(x[p])<<endl;
for(int i:x[p]){
y[i].erase(p);
}
x[p].clear();
}else{
cout<<sz(y[p])<<endl;
for(int i:y[p]){
x[i].erase(p);
}
y[p].clear();
}
}
return 0;
}
E. Popcount Sum 3
题意
多测,共 T T T 组数据。
给你整数 N N N 和 K K K,找到所有满足以下条件的数 x x x 的和:
- popcount ( x ) = K \text{popcount}(x)=K popcount(x)=K 且 $ x\le N$,其中 popcount(x) \text{popcount(x)} popcount(x) 表示 x x x 在二进制形式中 1 1 1 的数量
思路
提供一个不是 DP \text{DP} DP 的做法。为了方便表示,设 998244353 = M 998244353=M 998244353=M;
对于 N N N 二进制形式中每个是 1 1 1 的位数 d d d(从后往前数,最右边的是第 1 1 1 位),把它之前(不包括自己)的所有是 1 1 1 的位都默认设成 1 1 1,设之前有 c n t cnt cnt 位是 1 1 1(可以预处理 出前面是 1 1 1 的数位表示的数的总和,即 s u m = ∑ i = [ x ] 在二进制形式下的位数 d + 1 2 d − 1 sum=\sum_{i=[x]在二进制形式下的位数}^{d+1} 2^{d-1} sum=∑i=[x]在二进制形式下的位数d+12d−1),在之后(不包括自己)的共 d − 1 d-1 d−1 位中任意放入 K − c n t K-cnt K−cnt 个 1 1 1,很明显,方案数为 C d − 1 K − c n t \text{C}_{d-1}^{K-cnt} Cd−1K−cnt。我们把这些方案全部列出来:
下图展示了
k
−
c
n
t
=
3
,
d
−
1
=
5
k-cnt=3,d-1=5
k−cnt=3,d−1=5 的情况 :
它是
C
d
−
1
K
−
c
n
t
\text{C}_{d-1}^{K-cnt}
Cd−1K−cnt 个长度为
K
−
c
n
t
K-cnt
K−cnt 的序列,每个数在
1
∼
d
−
1
1 \sim d-1
1∼d−1 之间,可以证明,
1
∼
d
−
1
1 \sim d-1
1∼d−1 之间的每一个数(每一位)出现的次数都一样,即每个数字(每一位)出现了
t
=
C
d
−
1
K
−
c
n
t
×
(
K
−
c
n
t
)
d
−
1
t=\displaystyle \frac{\text{C}_{d-1}^{K-cnt} \times (K-cnt)}{d-1}
t=d−1Cd−1K−cnt×(K−cnt) 次:
第
i
i
i 位表示的数为
2
i
2^i
2i,则本次答案增加
Δ
=
∑
i
=
1
d
−
2
2
i
×
t
+
C
d
−
1
K
−
c
n
t
×
s
u
m
=
C
d
−
1
K
−
c
n
t
×
(
K
−
c
n
t
)
d
−
1
×
(
2
d
−
1
−
1
)
+
C
d
−
1
K
−
c
n
t
×
s
u
m
\Delta=\sum_{i=1}^{d-2}2^i\times t+\text{C}_{d-1}^{K-cnt}\times sum =\displaystyle \frac{\text{C}_{d-1}^{K-cnt} \times (K-cnt)}{d-1}\times (2^{d-1}-1)+\text{C}_{d-1}^{K-cnt}\times sum
Δ=∑i=1d−22i×t+Cd−1K−cnt×sum=d−1Cd−1K−cnt×(K−cnt)×(2d−1−1)+Cd−1K−cnt×sum。
如果 c n t = K cnt=K cnt=K 就把答案加上 s u m sum sum 并跳出循环。
注意点:
-
因为先取模再除法可能会出错,所以我们把除法换成乘法逆元,即 1 d − 1 m o d M = ( d − 1 ) M − 2 m o d M \frac{1}{d-1} \bmod M=(d-1)^{M-2}\bmod M d−11modM=(d−1)M−2modM(可以用费马小定理证明)。
-
同理, C n k \text{C}_n^k Cnk 也需要预处理,预处理出 f a c t i = i ! m o d M fact_i=i! \bmod M facti=i!modM, r e v i = 1 i ! m o d M rev_i=\frac{1}{i!}\bmod M revi=i!1modM, C n k = f a c t n × r e v k × r e v n − k \text{C}_n^k=fact_n\times rev_k\times rev_{n-k} Cnk=factn×revk×revn−k。
-
记得开 long long \text{long long} long long,计算幂要用快速幂,不然超时。
C++ 代码
#include<bits/stdc++.h>
#define int long long
#define mpr make_pair
#define pb push_back
#define sz(v) (int)v.size()
using namespace std;
const int MOD=998244353;
int n,k;
int fact[64],rev[64];
int pw(int a,int b){
int res=1;
while(b){
if(b&1) res=res*a%MOD;
a=a*a%MOD;
b>>=1;
}
return res;
}
int C(int n,int k){
if(n<k) return 0;
return fact[n]*rev[k]%MOD*rev[n-k]%MOD;
}
void solve(){
cin>>n>>k;
vector<bool> v;
while(n!=0){
v.pb(n&1);
n>>=1;
}
reverse(v.begin(),v.end());
int ans=0,cnt=0,sum=0;
for(int i=0;i<sz(v);i++){
if(v[i]==0) continue;
int dig=sz(v)-i;
ans=(ans+(pw(2,dig-1)-1)*(C(dig-1,k-cnt)*(k-cnt)%MOD*pw(dig-1,MOD-2)%MOD)%MOD)%MOD;
ans=(ans+C(dig-1,k-cnt)*sum)%MOD;
cnt++;
sum+=pw(2,dig-1);
if(cnt==k){
ans=(ans+sum)%MOD;
break;
}
}
cout<<ans<<endl;
}
signed main(){
fact[1]=fact[0]=rev[0]=1;
for(int i=2;i<=61;i++){
fact[i]=fact[i-1]*i%MOD;
}
rev[61]=pw(fact[61],MOD-2);
for(int i=60;i>=1;i--){
rev[i]=rev[i+1]*(i+1)%MOD;
}
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}