1001
lucas
定理,按位贪心
给一堆组合数,每个组合数 n n n是固定的, k k k在 [ 0 , L i ] [0,L_i] [0,Li]范围内,要他们乘起来模2余1,问有多少种方案,
乘起来余数是1,也就是每一个余数都是1。组合数模2余1,根据lucas
定理的推论,
C
(
n
,
k
)
%
2
=
1
C(n,k)\%2=1
C(n,k)%2=1等价于
n
&
k
=
k
n\&k=k
n&k=k。具体分析一下,如果
n
n
n在某一位是
1
1
1,
k
k
k在这一位也必须是
1
1
1,如果
n
n
n在某一位为0,
k
k
k在这一位随意。总的方案数就是每一个组合数的方案数乘起来,所以关键是求给定
n
n
n,
k
在
[
0
,
L
i
]
k在[0,L_i]
k在[0,Li]内,有多少个
k
k
k满足
n
&
k
=
k
n\&k=k
n&k=k
有上界,每一位有一些约束,求数字总数,显然按位贪心,枚举上界的前缀,如果有一位可以是0或1,枚举前缀的话应该选1,那么如果这里选0了,后面每一位都可以随便选,因此答案加上 2 k 2^k 2k, k k k是后面可以随意选的位数
void solve(){
int n;
cin>>n;
vector<int>a(n+1),b(n+1);
ll ans=1;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
ll cnt=0,tot=__builtin_popcount(a[i]);
int j;
for(j=30;j>=0;j--){
int x=a[i]>>j&1;
int y=b[i]>>j&1;
if(x)tot--;
if(x&&y){
cnt+=1<<tot;
cnt%=mod;
}
else if(!x&&y){
cnt+=1<<tot;
cnt%=mod;
break;
}
}
if(j<0)cnt++;
// cout<<cnt<<' ';
ans=ans*cnt%mod;
}
cout<<ans<<'\n';
}
1003
模拟,优先队列
能力有 m m m个维度,有 n n n个面试,需要每个维度都超过面试的要求才能过,过了之后每个维度都能获得增强,可以规划面试顺序,问能否通过所有面试?
只问能不能通过所有的面试,并且已经通过的面试,只要计算上能力的增强,对后面的面试就没有影响了,所以可以贪心,先把所有能过的面试都过了,计算能力增加,然后再看有哪些面试能过,直到剩余面试过不了或全部过了
先思考一个维度,显然应该按要求升序排序,先看要求低的。有多个维度,需要每个维度都小于等于当前能力,才能通过这个面试,所以对于每个面试,需要记录有多少个维度小于等于当前能力,当达到 n n n的时候,说明这个面试可以过了,加入一个队列。每次从队列里取一个能过的面试,结算能力提升。
void solve(){
int n,m;
cin>>n>>m;
vi a(m+1),cnt(n+1);
vvi c(n+1,vi(m+1)),w(n+1,vi(m+1));
rep(i,1,m)cin>>a[i];
rep(i,1,n){
rep(j,1,m){
cin>>c[i][j];
}
rep(j,1,m){
cin>>w[i][j];
}
}
queue<int>q;
priority_queue<pii,vector<pii>,greater<pii>>pq[m+1];
rep(i,1,n){
rep(j,1,m){
if(a[j]>=c[i][j]){
cnt[i]++;
}
else{
pq[j].push({c[i][j],i});
}
}
if(cnt[i]==m){
q.push(i);
}
}
int tot=0;
while(q.size()){
int u=q.front();
q.pop();
tot++;
rep(i,1,m){
a[i]+=w[u][i];
while(pq[i].size()&&a[i]>=pq[i].top().fi){
auto t=pq[i].top();
pq[i].pop();
if(++cnt[t.se]==m){
q.push(t.se);
}
}
}
}
if(tot==n)cout<<"YES\n";
else cout<<"NO\n";
}
1005
并查集,图
一条链,原本的边全断了,连上了一些新边,问最少恢复几个原本边,也就是 ( a i , a i + 1 ) (a_i,a_{i+1}) (ai,ai+1)这些边,可以让图联通?
给几个连通块,问全部联通至少需要加几个边,有一个结论就是类似生成树的思想, n n n个连通块只需要 n − 1 n-1 n−1条边。
但这个结论是建立在任意两个典之间可以连边的基础上的,而本题只能在编号相邻的点连边,还成立吗?
实际上成立,因为你把两个不在一个连通块的点连接,连通块数就会减一,这样 n n n个连通块 n − 1 n-1 n−1次连接就会变成一个连通块
void solve(){
int n;
cin>>n;
vector<int>a(n+1),f(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
f[i]=i;
}
auto &&find=[&](auto&&find,int x)->int{
if(f[x]==x)return x;
return f[x]=find(find,f[x]);
};
auto merge=[&](int x,int y)->void{
int fx=find(find,x),fy=find(find,y);
if(fx!=fy){
f[fx]=fy;
}
};
for(int i=1;i<=n;i++){
if(i+a[i]<=n){
merge(i,i+a[i]);
}
if(i-a[i]>=1){
merge(i,i-a[i]);
}
}
int ans=0;
for(int i=1;i<=n;i++){
if(find(find,i)==i){
ans++;
}
}
cout<<ans-1<<'\n';
}
1007
可持久化 01 t r i e 01trie 01trie模板
每次问一个区间内的元素,和 x x x的异或的最大值?问一个集合里的元素和一个新元素的异或最大值,可以 01 t r i e 01trie 01trie,但这里每次询问都是一个区间内的元素,可以用类似主席树的思想建立 01 t r i e 01trie 01trie,对每个节点记录一下这个元素的前缀和,这样查询 l r lr lr区间时,每一步都用 t r [ r ] . c n t − t r [ l − 1 ] . c n t tr[r].cnt-tr[l-1].cnt tr[r].cnt−tr[l−1].cnt就是这个区间内的元素形成的集合
class PersistentTrie {
private:
struct Node {
int child[2]; // 存储左右子节点在nodes数组中的下标
int cnt;
Node() : cnt(0) { child[0] = child[1] = 0; }
};
vector<Node> nodes; // 存储所有节点
vector<int> roots; // 存储每个版本的根节点下标
const int MAXBIT = 30;
int newNode() {
nodes.push_back(Node());
return nodes.size() - 1;
}
int newNode(const Node& old) {
nodes.push_back(old);
return nodes.size() - 1;
}
int insert(int nodeIdx, int val, int bit) {
int currIdx = newNode(nodes[nodeIdx]);
nodes[currIdx].cnt++;
if(bit < 0) return currIdx;
int b = (val >> bit) & 1;
if(!nodes[currIdx].child[b]) {
nodes[currIdx].child[b] = newNode();
}
nodes[currIdx].child[b] = insert(nodes[currIdx].child[b], val, bit-1);
return currIdx;
}
int queryMax(int r1, int r2, int val, int bit) {
if(bit < 0) return 0;
if(!r1 && !r2) return 0;
int b = (val >> bit) & 1;
int want = b ^ 1;
int cnt_want = 0;
if(r2 && nodes[r2].child[want]) {
cnt_want += nodes[nodes[r2].child[want]].cnt;
}
if(r1 && nodes[r1].child[want]) {
cnt_want -= nodes[nodes[r1].child[want]].cnt;
}
if(cnt_want > 0) {
return (1 << bit) | queryMax(
r1 ? nodes[r1].child[want] : 0,
r2 ? nodes[r2].child[want] : 0,
val, bit-1
);
} else {
return queryMax(
r1 ? nodes[r1].child[b] : 0,
r2 ? nodes[r2].child[b] : 0,
val, bit-1
);
}
}
public:
PersistentTrie() {
// 初始化空根,下标0表示空节点
nodes.reserve(30*2e5);
roots.reserve(30*2e5);
nodes.push_back(Node());
roots.push_back(0);
}
// 插入一个数,返回新版本
int insert(int val) {
roots.push_back(insert(roots.back(), val, MAXBIT));
return roots.size() - 1;
}
// 查询区间[l,r]中与val异或的最大值
int queryMaxXor(int l, int r, int val) {
if(l > r || l < 0 || r >= roots.size()) return 0;
return queryMax(roots[l], roots[r+1], val, MAXBIT);
}
int size() {
return roots.size() - 1;
}
};
// 测试代码
void solve() {
PersistentTrie trie;
int n,q;
cin>>n>>q;
for(int i=0;i<n;i++){
int x;
cin>>x;
trie.insert(x);
}
for(int i=0;i<q;i++){
int l,r,x;
cin>>l>>r>>x;
cout<<trie.queryMaxXor(l-1,r-1,x)<<'\n';
}
}
1009
启发式合并,映射
有几种操作:一个集合合并到另一个集合,一个人合并到另一个集合,两个集合交换编号,查询一个人所在集合。
关键是这个两个集合交换编号,这乍看可以直接 s w a p ( s i , s j ) swap(s_i,s_j) swap(si,sj),但是后面查询查的是一个元素属于哪个集合,单纯 s w a p swap swap集合,元素所在的集合编号没有修改,如果真的去修改复杂度会炸。
这里的解决方案是,在集合上面再增加一层编号,每个元素只保存它属于哪个集合,每个集合有个编号,交换时只要交换编号,并不真的交换集合。具体来说,我们可以维护每个编号对应的是哪个实际集合,然后直接对于这个映射数组进行交换即可
但这样查询时,只能定位到一个元素属于哪个实际集合,我们想知道编号,还需要增加一个集合到编号的映射,也就是保存一个集合现在的编号是多少
还有一个合并操作,这显然启发式合并。由于合并给出的是编号,不是实际集合,所以需要利用编号到集合的映射确定实际上是哪个集合,再合并。如果是吧大的合并到小的,需要先把小的合并到大的在进行一个交换,交换就用和前面类似的思路就行
void solve(){
int n,q;
cin>>n>>q;
vector<int>belong(n+1),id(n+1),id1(n+1);
set<int>s[n+1];
for(int i=1;i<=n;i++){
id[i]=id1[i]=i;
belong[i]=i;
s[i].insert(i);
}
for(int i=1;i<=q;i++){
int op,x,y;
cin>>op;
if(op==1){
cin>>x>>y;
if(x==y)continue;
if(s[id[x]].size()>s[id[y]].size()){
for(int t:s[id[y]]){
s[id[x]].insert(t);
belong[t]=id[x];
}
}
else{
for(int t:s[id[x]]){
s[id[y]].insert(t);
belong[t]=id[y];
}
swap(id1[id[x]],id1[id[y]]);
swap(id[x],id[y]);
// swap(s[id[x]],s[id[y]]);
}
}
else if(op==2){
cin>>x>>y;
s[belong[x]].erase(x);
s[id[y]].insert(x);
belong[x]=id[y];
}
else if(op==3){
cin>>x>>y;
if(x==y)continue;
swap(id1[id[x]],id1[id[y]]);
swap(id[x],id[y]);
// swap(s[id[x]],s[id[y]]);
}
else{
cin>>x;
// cout<<belong[x]<<' ';
cout<<id1[belong[x]]<<'\n';
}
}
}