牛客练习赛129(欧拉筛、快速幂、排列组合、二进制、倍增、线段树、状压DP)

牛客练习赛129(欧拉筛、快速幂、排列组合、二进制、倍增、线段树、状压DP)


F题不会,看了别人的代码,给大家加个注释看一下吧,


A. 数数(欧拉筛)

题目中,“奇数” = 质数的整次幂。找到所有的质数,维护其小于等于 n 的质数的整次幂的个数即可。

#include<bits/stdc++.h>

using namespace std;

const int maxn = 5e6 + 10;
int v[maxn], prime[maxn], cnt = 0;
int f[maxn];

int main(){
    
    int n;
    cin >> n;
    for(int i = 2; i <= n; i++){
        if(v[i] == 0) prime[++cnt] = i;
        for(int j = 1; j <= cnt && i * prime[j] <= n; j++){
            v[i * prime[j]] = 1;
            if(i % prime[j] == 0) break;
        }
    }
    int res = n-1;
    for(int i = 1; i <= cnt; i++){
        for(long long j = prime[i]; j <= n; j *= prime[i]){
            res--;
        }
    }
    
    cout << res << endl;
    
    return 0;
}

B. 三位出题人(快速幂、排列组合)

根据题意:

  1. 每个题目可能的出题方案为 m 个人全排列 - 所有人都不选 - 所有人都选,即 2 m − 2 2^m-2 2m2。( 2 m = ∑ 0 m C ( i , m ) 2^m = \sum_0^mC(i, m) 2m=0mC(i,m) )

  2. 每个题的出题人方案是相互独立的,故而 r e s = ( 2 m − 2 ) n res = (2^m - 2)^n res=2m2n

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

const ll mod = 1e9 + 7;

ll qpow(ll a, ll b){
    ll res = 1;
    while(b){
        if(b&1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

int main(){
    int ncase;
    cin >> ncase;
    
    while(ncase--){
        int n, m;
        cin >> n >> m;
        ll tmp = (qpow(2, m) - 2 + mod) % mod;
        ll res = qpow(tmp, n);
        cout << res << endl;
    }
    
    return 0;
}

C. 和天下(二进制)

对于k的二进制,按位划分。设 k = 10410 = 11010002

  1. ai 的范围是 0 ~ 1e18,大概就是 263 的样子,考虑 63 个二进制位。

  2. 设 p = 2x ,且 p > k。满足 ai & p = p 的所有 ai 均可以互连,放入到同一个set中。

  3. 对于不满足条件2.的情况,划分规则如下图(注意划分到的flag 和 k 二进制比较):

    在这里插入图片描述

  4. 对于 k 为偶数时,上述划分方案,不会覆盖到 ai = k的情况,需要多加一个 flag = k

  5. 对于每一个 ai,如果其属于多个set,把不同set合并。

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

const int maxn = 2e5 + 10;
ll a[maxn], flag[100];
int f[maxn], sz[maxn];

int find(int x){
	if(f[x] == x) return x;
	else return f[x] = find(f[x]);
}

void join(int x, int y){
	int fx = find(x);
	int fy = find(y);
	
	if(fx != fy){
		f[fx] = fy;
		sz[fy] += sz[fx];
	}
}

int main() {

	int ncase;
	cin >> ncase;
	while(ncase--) {
		
		for(int i = 0; i <= 63; i++) flag[i] = -1, f[i] = i, sz[i] = 0;
		
		ll n, k;
		cin >> n >> k;
		for(int i = 1; i <= n; i++) cin >> a[i];
		
		ll num = 0;
		for(int i = 62; i >= 0; i--){	// 划分 flag
			ll p = 1ll << i;
			if(p > k) flag[i] = p;
			else{
				num += p;
				if(num > k) flag[i] = num;
				num -= p;
				if((k & p) == p) num += p;
			}
		}
		flag[63] = flag[0] != k ? k : -1;	// 处理 flag是否有 k
		
// 这部分代码是输出每个flag的,可以自己debug一下
//		for(int i = 63; i >= 0; i--){
//			cout << i << " " << flag[i] << " ";
//			if(flag[i] != -1){
//				for(int j = 62; j >= 0; j--){
//					ll p = 1ll << j;
//					if((flag[i] & p) == p) cout << 1;
//					else cout << 0;
//				}				
//			}
//			cout << endl;
//		}
		
		for(int i = 1; i <= n; i++){
			for(int j = 0; j <= 63; j++){
				// 维护sz, 必须先维护,注意运算符优先级 
				if(flag[j] != -1 && (a[i] & flag[j]) == flag[j]){
					sz[j]++;
					break;
				}
			}
		}
		for(int i = 1; i <= n; i++){	// 并查集
			int u = -1;
			for(int j = 0; j <= 63; j++){
				if(flag[j] != -1 && (a[i] & flag[j]) == flag[j]){
					if(u == -1) u = j;
					else join(u, j);
				}
			}
		}
		int res = 1;
		for(int i = 63; i >= 0; i--){
			if(find(i) == i){
				res = max(res, sz[i]);
			}
		}
		cout << res << endl;
	}

	return 0;
}

/*
2
5 104
5 21 1 8 10
*/

D. 搬家(倍增)

n e x t ( i ) next(i) next(i)表示,以 i i i 为起点,下一个箱子开始的下标。显然,二分可以快速求出每一个 i 的 n e x t ( i ) next(i) next(i)

f ( i , j ) f(i,j) f(i,j) 表示以 i i i 为起点, 第 2 j 2^j 2j 个箱子装满后,下一个箱子的下标。

则: f ( i , 0 ) = n e x t ( i ) f(i,0) = next(i) f(i,0)=next(i) f ( i , j ) = f ( f ( i , j − 1 ) , j − 1 ) f(i,j) = f(f(i, j-1), j-1) f(i,j)=f(f(i,j1),j1),维护出 f f f 即可。

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

const int maxn = 2e5 + 10;
ll a[maxn], pre[maxn], f[maxn][30];


int main() {

	int ncase;
	cin >> ncase;
	while(ncase--) {
		
        int n, m, k;
        cin >> n >> m >> k;
        for(int i = 1; i <= n; i++) cin >> a[i];
        for(int i = 1; i <= n; i++) pre[i] = pre[i-1] + a[i];
        
        for(int i = 1; i <= n; i++){
            int l = i, r = n, pos = -1;
            while(l <= r){
                int mid = l + r >> 1;
                if(pre[mid] - pre[i-1] <= k){
                    pos = mid;
                    l = mid + 1;
                } 
                else r = mid - 1;
            }
            if(pos == -1) f[i][0] = i;   // 使用2^i 个箱子后,要装的下一个物品的pos
            else f[i][0] = pos+1;
        }
        
        for(int j = 1; (1 << j) <= n; j++){ // 倍增
            for(int i = 1; i <= n; i++){
                if(f[i][j-1] > n) f[i][j] = n+1; // 越过 n 了直接 n+1 即可
                else f[i][j] = f[f[i][j-1]][j-1]; 
            }
        }
        
        int res = 0;
        for(int i = 1; i <= n; i++){
            int pos = i, mm = m;
            for(int j = 20; j >= 0; j--){
                if((1 << j) <= mm){
                	mm -= (1 << j);
                	pos = f[pos][j]; 
				}
                if(pos == n+1) break; // 已经到了最后一个
            }
            res = max(res, pos-i);
        }
        
        cout << res << endl;
	}

	return 0;
}

E. Alice and Bod(线段树)

操作一查询任意子串是否对称(区间查询),再考虑操作二是要修改的(区间修改),显然要线段树来解。

操作一:查询子串是否对称,查询 S[l, … ,r] 和 S[r, … , l] 的哈希值是否一致,维护两个线段树,对字符串s 在线段树上维护一个哈希值,一个是字符串s的反转在线段树维护一个哈希值。 (S[r, … , l] 表示字符串S[l, … ,r]的反转)

对于一个位置,维护26个哈希,对应 ‘a’ - ‘z’,如果当前位置字符为 c,则 hash(c ) = 1。可以参考下表:

Scacba
PP0P1P2P3P4
‘a’0 * p00 * p0 + 1 * p10 * p0 + 1 * p1 + 0 * p20 * p0 + 1 * p1 + 0 * p2 + 0 * p30 * p0 + 1 * p1 + 0 * p2 + 0 * p3 + 1 * p4
‘b’0 * p00 * p0 + 0 * p10 * p0 + 0 * p1 + 0 * p20 * p0 + 0 * p1 + 0 * p2 + 1 * p30 * p0 + 0 * p1 + 0 * p2 + 1 * p3 + 0 * p4
‘c’1 * p01 * p0 + 0 * p11 * p0 + 0 * p1 + 1 * p21 * p0 + 0 * p1 + 1 * p2 + 0 * p31 * p0 + 0 * p1 + 1 * p2 + 0 * p3 + 0 * p4

操作二:区间加 x,且加x时取mod。对于一个位置,其字符修改后,哈希值整体移动 x 即可。

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

const ll base = 2333;
const ll mod = 1e9 + 7;
const int maxn = 1e5 + 10;

typedef struct Node{
	ll num[26];
	int len = 0, lazy = 0;
    Node (){
        for (int i = 0; i < 26; i++) {
            num[i] = 0;
        }
        len = lazy = 0;
    }
} node;

node tree[2][maxn << 2];
ll p[maxn];
string s[2];

void push_up(int op, int root){
	int lroot = root << 1, rroot = root << 1 | 1;
	for(int i = 0; i < 26; i++){
		tree[op][root].num[i] = tree[op][rroot].num[i] * p[tree[op][lroot].len] % mod;
		tree[op][root].num[i] = (tree[op][root].num[i] + tree[op][lroot].num[i]) % mod;
	}
	tree[op][root].len = tree[op][lroot].len + tree[op][rroot].len;
}

node merge(node A, node B){
	node res;
	for(int i = 0; i < 26; i++){
		res.num[i] = B.num[i] * p[A.len] % mod;
		res.num[i] = (res.num[i] + A.num[i]) % mod;
	}
	res.len = A.len + B.len;
	return res;
}

void push_down(int op, int root){
	if(tree[op][root].lazy){
		int lroot = root << 1, rroot = root << 1 | 1;
		tree[op][rroot].lazy = (tree[op][rroot].lazy + tree[op][root].lazy) % 26;
		tree[op][lroot].lazy = (tree[op][lroot].lazy + tree[op][root].lazy) % 26;
		node tmp = tree[op][lroot];
		for(int i = 0; i < 26; i++) tree[op][lroot].num[(i+tree[op][root].lazy) % 26] = tmp.num[i];
		tmp = tree[op][rroot];
		for(int i = 0; i < 26; i++) tree[op][rroot].num[(i+tree[op][root].lazy) % 26] = tmp.num[i];
		tree[op][root].lazy = 0;
	}
}

void build(int op, int root, int l, int r){
	if(l == r){
		tree[op][root].num[s[op][l]-'a'] = 1;
		tree[op][root].len = 1;
		return;
	}
	int mid = l + r >> 1;
	build(op, root<<1, l, mid);
	build(op, root<<1|1, mid+1, r);
	push_up(op, root);
}

void update(int op, int root, int l, int r, int L, int R, int x){ // 区间 L,R 加 x 
	if(L <= l && r <= R){
		node tmp = tree[op][root];
		for(int i = 0; i < 26; i++){	// 哈希移动
			tree[op][root].num[(i+x) % 26] = tmp.num[i];
		}
		tree[op][root].lazy = (tree[op][root].lazy + x) % 26; // 注意 x 可能很多 
		return;
	}
	push_down(op, root);
	int mid = l + r >> 1;
	if(L <= mid) update(op, root<<1, l, mid, L, R, x);
	if(mid < R) update(op, root<<1|1, mid+1, r, L, R, x);
	push_up(op, root);
}

node query(int op, int root, int l, int r, int L, int R){ // 查询区间 L,R 
// 	cout << op << " " << root << " " << l << " " << r << " " << L << " " << R << endl; 
	if(L <= l && r <= R) return tree[op][root];
	push_down(op, root);
	int mid = l + r >> 1;
	node tmp;
	if(L <= mid) tmp = merge(tmp, query(op, root<<1, l, mid, L, R));
	if(mid < R) tmp = merge(tmp, query(op, root<<1|1, mid+1, r, L, R));
	return tmp;	
}

int main() {
	
	p[0] = 1;
	for(int i = 1; i < maxn; i++) p[i] = p[i-1] * base % mod;
	
	int n, m;
	cin >> n >> m;
	cin >> s[0];
	s[1] = " ";
	for(int i = 0; i < n; i++) s[1] = s[1] + s[0][n-1 - i];
	s[0] = " " + s[0];
	
	build(0, 1, 1, n);
	build(1, 1, 1, n);
	
	while(m--){
		int op;
		cin >> op;
		if(op == 1){
			int l, r;
			cin >> l >> r;
			node t1 = query(0, 1, 1, n, l, r);
			node t2 = query(1, 1, 1, n, n+1-r, n+1-l);
			int res = 1;
			for(int i = 0; i < 26; i++) if(t1.num[i] != t2.num[i]) res = 0;
			cout << (res ? "YES" : "NO") << endl;
		}
		else{
			int l, r, x;
			cin >> l >> r >> x;
            x %= 26;
			update(0, 1, 1, n, l, r, x);
			update(1, 1, 1, n, n+1-r, n+1-l, x);
		}
	}
	
	return 0;
}

F. 网络通路(状压DP)

直接看代码的注释。

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

ll e[20][20];
ll dp[(1<<16)][20], f[(1<<16)][20];

ll func(int x){
	ll cnt = 0;
	while(x){
		cnt += (x & 1);
		x >>= 1;
	}
	return cnt;
}

int main() {
	
	memset(e, 0x01, sizeof(e));
	memset(dp, 0x01, sizeof(dp));
	memset(f, 0x01, sizeof(f));
	
	int n, m;
	cin >> n >> m;
	ll u, v, c;
	for(int i = 1; i <= m; i++){
		cin >> u >> v >> c;
		u--; v--;
		e[u][v] = e[v][u] = min(e[v][u], c);    // 重边和自环
	}
	
	for(int i = 0; i < n; i++) dp[(1<<i)][i] = 0;	// 只有一个点 i 的集合初始化为零 
	
	for(int i = 1; i < (1<<n); i++){
		for(int j = 0; j < n; j++){ 		// 维护当前集合,每次 
			if((1<<j) & i){					// 当前集合i有点 j 
				int mask = i ^ (1 << j); 	// 得到集合i去掉点j的子集
				for(int p = mask; p; p = (p-1)&mask){	// 枚举子集p 通过点 j 连接子集(i^p),从而维护子集 i 的值  
					dp[i][j] = min(dp[i][j], dp[i ^ p][j] + f[p][j]);
				} 
			}
		}
		for(int j = 0; j < n; j++){
			if((1<<j) & i){
				for(int k = 0; k < n; k++){
					if((1<<k) & i) continue;
					if(e[j][k] > 1e9) continue;
					f[i][k] = min(f[i][k], dp[i][j] + e[j][k] * func(i) * (n - func(i)));	
					// 通过边 j-k,维护子集 i 通过点 j 向外延申一个点 k 时的贡献 
				}
			}
		}
	}
	
	ll res = 2e18;
	for(int i = 0; i < n; i++) res = min(res, dp[(1<<n)-1][i]);
	if(res > 1e9) res = -1;
	cout << res << endl;
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值