蓝桥杯题单day4【题解】

回路计数

思路:

由于楼的个数很少,我们考虑用状压dp来做。即用长度为21的二进制数表示21个楼的状态,第x位为1表示第x+1号楼已经被走过了。

f[ j ][ i ]表示当前所在的楼号为 j,且当前楼的状态为 i 时的种类数。

对于每个确定的 i,首先要判断这个 j 是否合法,即在状态 i 下 j 号楼有没有被走过,因为我们当前的楼只能是走过的楼。

然后就枚举下一个走哪栋楼,过程中判断是否能走到以及是否没走过。

详情请见代码

代码:

这是道填空题,只用输出答案即可。

网站上貌似开不了这么大的空间,本地跑完直接输出答案就好啦。

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

const int N = 22, M = 1 << 21;
int f[N][M];
bool e[N][N];

signed main(){
	
	for(int i = 1; i <= 21; i ++ ){
		for(int j = 1; j <= 21; j ++ ){
			if(__gcd(i, j) == 1)
				e[i - 1][j - 1] = 1;
		}
	}
	
	f[0][1] = 1; //从1号教学楼出发
	
	for(int i = 1; i < (1 << 21); i ++ ){
		for(int j = 0; j < 21; j ++ ){	//枚举当前所在楼
			if(!((i >> j) & 1))		//若当前楼在状态i下没有走到 
				continue;
			
			//从j号楼走到k号楼 
			for(int k = 0; k < 21; k ++ ) {
				if((i >> k) & 1 || !e[j][k])	//若k走过了或者j走不到k 
					continue; 
				f[k][i | (1 << k)] += f[j][i]; 
			}
		}
	} 
	
	int ans = 0;
	for(int i = 0; i < 21; i ++ ){
		ans += f[i][(1 << 21) - 1];
	}
	
	cout << ans << "\n";
} 


糖果

思路:

类似背包,对于每个糖包我们可以选择吃或者不吃

中间用长度为m的二进制数来表示各种口味的状态,第x位为1表示第x+1种口味吃过了,为0表示没有吃过。

f[ i ] 表示口味状态为 i 时最少需要吃的糖包数,剩下部分的和01背包问题一样

代码:

空间貌似只能开到1 << 21,再加一维的话空间会爆掉,所以实现时用滚动数组降维

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

const int N = 110, M = 21;
int f[1 << M];

signed main(void){
	int n, m, k;
	cin >> n >> m >> k;
	
	memset(f, 0x3f, sizeof f);
	f[0] = 0; 
	
	for(int i = 1; i <= n; i ++ ){
		int x = 0;		//第i包糖果的状态
		for(int j = 1; j <= k; j ++ ){
			int t;
			cin >> t;
			x |= (1 << (t - 1));
		} 

		//各种口味的糖果状态 
		for(int j = 0; j < (1 << m); j ++){
			int t = x | j;	//吃了这包糖果后当前糖果口味的状态
			f[t] = min(f[t], f[j] + 1); 	//选择:吃与不吃 
		}
	}
	
	int ans = f[(1 << m) - 1];
	if(ans > n) ans = -1;
	cout << ans << "\n";
	return 0;
} 

生命之树

思路:

树形dp的套路就是先处理子树,处理完之后再层层往上。

f[ i ]表示以 i 为根节点的子树的最大权值和,其中 i 号点一定包含于以 i 为根节点的子树中

对于 i 号点,为了找到 f[ i ],我们贪心的选择其大于0的子树

代码:

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

const int N = 1e5 + 10;
vector<int> e[N];
int f[N], a[N], ans;

void dfs(int u, int fa){
	f[u] = a[u];
	for(auto v : e[u]){
		if(v == fa) continue;
		dfs(v, u);
		if(f[v] > 0) 	//贪心选择,大于0的我们都要 
			f[u] += f[v];
	}
	
	ans = max(ans, f[u]);
}

signed main(){
	int n;
	cin >> n;
	for(int i = 1; i <= n; i ++ ){
		cin >> a[i];
	}
	
	for(int i = 1; i < n; i ++ ){
		int x, y;
		cin >> x >> y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	
	dfs(1, 0);
	
	cout << ans << "\n";
} 

没有上司的舞会

思路:

由于每个节点有选与不选两种选择,且子节点的选择会影响到当前节点,所以我们定义状态时需要多一维来表示节点的选择:

        f[x][0]表示以x为根的子树,且x不参加舞会的最大快乐值

        f[x][1]表示以x为根的子树,且x参加了舞会的最大快乐值

对于当前节点x,如果我们不选,那么我们就可以选择x的子节点,当然我们也可以选择不选,所以    f[u][0] += max(f[v][1], f[v][0]);

如果我们选择x节点,那么对于x的子节点,我们只能不选,所以f[u][1] += f[v][0];

代码:

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

const int N = 1e5 + 10;
vector<int> e[N];
int f[N][2], a[N];
bool vis[N];	//判断是否为根 

void dfs(int u){
	f[u][0] = 0;	//f[u][0]表示u不选 
	f[u][1] = a[u];	//f[u][1]表示u选 
	for(auto v : e[u]){
		dfs(v);		//先处理子树 
		f[u][0] += max(f[v][1], f[v][0]);
		f[u][1] += f[v][0]; 
	}
}

signed main(){
	int n;
	cin >> n;
	for(int i = 1; i <= n; i ++ ){
		cin >> a[i];
	}
	
	for(int i = 1; i < n; i ++ ){
		int x, y;
		cin >> x >> y;
		e[y].push_back(x);
		vis[x] = 1; 
	}
	
	int root = 1;
	while(vis[root]) root ++;
	
	dfs(root);
	
	cout << max(f[root][0], f[root][1]);
} 

 二进制问题

思路:

可以参考https://blog.csdn.net/qq_46117575/article/details/125081583

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 60;

int n, K;
int f[N][N];

signed main()
{
    cin >> n >> K;
    
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j <= i; j ++ ) 
            if (!j) f[i][j] = 1;
            else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
    
    
    vector<int> nums;
    while (n) nums.push_back(n % 2), n /= 2;
    
    int res = 0;
    int last = 0;
    for (int i = nums.size() - 1; i >= 0; i -- ) 
    {
        int x = nums[i];
        if (x)
        {
            /*当第i位选0时*/
            res += f[i][K - last];
            /*当第i位选1时*/
            if (x == 1)
            {
                last ++ ;
                /*如果已经消耗1的数量大于规定的数量时,结束算法*/
                if (last > K) break;
            }
        }
        /*当为最后一位时,如果所消耗1的数量恰好等于规定数量时,即最后一位符合要求*/
        if (!i && last == K) res ++ ;
    }
    
	cout << res;
    
    return 0;
}

异或三角

思路:

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;

int m, k;
LL n;
int a[N], cnt;
LL f[31][2][8];
LL dfs(int u, int limit, int state) {
	if (!u) return state == 7;
	LL &t = f[u][limit][state];
	if (~t) return t;	
	LL res = 0;
	int up = limit ? a[u] : 1;

	for (int i = 0; i <= up; i ++) 
		if (!i) {
			res += dfs(u - 1, limit && i == up, state);
			if (state >= 6) 
			res += dfs(u - 1, limit && i == up, state | 1);
		}
		else {
			res += dfs(u - 1, limit && i == up, state | 2);
			res += dfs(u - 1, limit && i == up, state | 4);
		}
	
	return t = res; 	
}

LL dp(LL x) {
	cnt = 0;
	memset(f, -1, sizeof f);	
	while (x) {
		a[++cnt] = x & 1;
		x >>= 1;		
	}	
	return dfs(cnt, 1, 0) * 3;
}

int main() {
	int T;
	cin >> T;
	while (T -- ) {		
		cin >> n;
		cout << dp(n) << '\n';
	}
	return 0;
}

忠诚

思路:

不难发现这道题就是个RMQ问题,如果我们对于每次查询都使用暴力来做的话肯定会超时的,所以我们可以使用st表来优化。

st[ i ][ j ]表示的是区间[i, i + 2^j - 1]的最小值,即一个长度为2^j的区间的最值,它利用的是倍增思想,就能比较快(log)的查询出区间最值。

st建立的过程比较像区间dp,然后查询时由于k是取了log可能会有些损失,所以我们既要从左往右st[l][k],也要从右往左st[r - (1 << k) + 1][k]才能覆盖住整个查询区间。

代码:

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

const int N = 1e5 + 10;

int st[N][20], lg[N]; 
int n, m;

signed main(){
	cin >> n >> m;
	for(int i = 1; i <= n; i ++ )
		cin >> st[i][0];
	lg[0] = -1;
	for(int i = 1; i <= n; i ++ ) lg[i] = lg[i >> 1] + 1;
	
	for(int j = 1; j <= 20; j ++ ){
		for(int i = 1; i + (1 << j) - 1 <= n; i ++ ){
			st[i][j] = min(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
		}
	}
	
	while(m -- ){
		int l, r;
		cin >> l >> r;
		int k = lg[r - l + 1];
		cout << min(st[l][k], st[r - (1 << k) + 1][k]) << " ";
	}
}

机房

思路:

我们这里可以把1号点当做树根root,然后使用sum[x]表示x点发送消息到root所需要的时间。 很显然,x发送出去的时候经过自身的延迟后到达另外一个点,另外一个点在经过延迟到达下一个点,以此类推。。。

那么任意两个点之间的距离一般就是使用sum[x] + sum[y] - 2 * sum[lca(x, y)] 但是对于本题而言,当减去两倍的最近公共祖p先到root的距离时,就会把自身的一个延迟多减了一次,所以还需要加上p的出度。 所以本题正确计算距离的公式应该为: d = (sum[x] + sum[y] - 2 * sum[p] + dg[p]) 其中p表示x和y的最近公共祖先,dg[p]表示p点的出度

代码:

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;

const int N = 5e5 + 5;

vector<int> g[N];
int n, m, root;
int dep[N], fa[N][20];
int sum[N];

void dfs(int u, int p) {
    dep[u] = dep[p] + 1;
	fa[u][0] = p;
    sum[u] = sum[p] + (int)g[u].size();
    for (int i = 1; i < 20; i ++) 
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
		
    for(auto v : g[u]){
    	if (v == p)	continue; 
		dfs(v, u); 
	}

}

int LCA(int x, int y) {
    if(dep[x] < dep[y]) 
		swap (x, y);
		
    for (int i = 19; i >= 0; i --) 
        if (dep[fa[x][i]] >= dep[y]) 
            x = fa[x][i];
            
    if (x == y) return x;
    
    for (int i = 19; i >= 0; i --) 
        if (fa[x][i] != fa[y][i]) 
            x = fa[x][i], y = fa[y][i];
            
    return fa[x][0];
}

int main() {
    cin >> n >> m;
    for (int i = 1; i < n; i ++) {
        int u, v; 
		cin >> u >> v;
        g[u].push_back(v), g[v].push_back(u);
    }
    
    dfs(1, 0);
    
    while (m --) {
        int u, v; 
		cin >> u >> v;
        int p = LCA(u, v);
        cout << sum[u] + sum[v] - 2 * sum[p] + (int)g[p].size() << "\n";
    }a
    
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值