25杭电春季第三场

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 n1条边。

但这个结论是建立在任意两个典之间可以连边的基础上的,而本题只能在编号相邻的点连边,还成立吗?

实际上成立,因为你把两个不在一个连通块的点连接,连通块数就会减一,这样 n n n个连通块 n − 1 n-1 n1次连接就会变成一个连通块

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].cnttr[l1].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';
		}
		
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值