CF #774 ——C(二进制枚举),D(树形dp)

C. Factorials and Powers of Two

题意:

如果一个数为 2的幂次 或者是 阶乘数,那么定义这个数为漂亮数
现在给定一个数 n,问最少可以由多少个不同的漂亮数构成?
1 ≤ n ≤ 1 0 12 1 ≤ n ≤ 10^{12} 1n1012

分析:

已知任何一个数都可以用若干个 2的幂次数 相加而成,2的那些幂次数 可以用将这个数化为二进制后,1的位置和个数确定。

观察到 n 的范围不大,在这个范围内的阶乘数一共14个,所以可以遍历这些数到底用了哪几个。(二进制枚举)
然后减去这些阶乘数之后,判断需要用多少2的幂次数构成。
答案 与 阶乘数+2的幂次数个数 取min。

二进制枚举:
一共 n 个数,判断拿了哪几个。

for(int i=0; i<(1<<n); i++){ //一共2^n次情况,状态压缩为一个数。
	for(int j=0;j<n;j++){ //通过枚举这个数的二进制每一位来确定这个物品是否拿了
		if(i & (1<<j)) ... //如果第j个位置为1,那么就拿了第j个位置。
	}
}

完整Code:

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

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'

map<int,int> mp;

const int N = 200010, mod = 1e9+7;
int T, n, m, k;
int a[N];

int jc(int x){
	int ans=1;
	for(int i=1;i<=x;i++) ans*=i;
	return ans;
}

signed main(){
	Ios;
	int cnt=0;n = 1000000000000;
	for(int i=1;i<=n;i++)
	{
		int x = jc(i);
		if(x > n) break;
		a[++cnt] = x;
	}
	
	cin>>T;
	while(T--)
	{
		cin>>n;
		
		int ans = 1e9;
		for(int i=0;i<(1ll<<14);i++)
		{
			int cnt=0, sum=0;
			for(int j=0;j<14;j++)
			{
				if(i & (1<<j)) cnt++, sum+=a[j+1];
			}
			if(sum > n) break;
			
			int t = n-sum;
			for(int j=0;j<64;j++)
			{
				if(t & (1ll<<j)) cnt++;
			}
			ans = min(ans, cnt);
		}
		if(ans == 1e9) cout<<-1<<endl;
		else cout<<ans<<endl;
	}
	
	return 0;
}

D. Weight the Tree

题意:

给出一个 n 个节点的树,定义一个点是好的:其点权为其相邻的所有点的点权之和。
现在请你将给这 n 个节点赋值,使得 好点 的个数尽量多。同时,在好点个数相同的情况下,使得 n 个节点点权之和尽可能小。
输出 好点 的最多个数,点权之和,和每个点的赋值。

分析:

  • 当只有两个点的时候,两个点都可以作为好点;
  • 当点数不止两个的时候,相邻的两点不能全是好点:对于相邻的两个点a, b,在这个树中,至少有一个点会与其他点相连,那么其相邻点就为另一个点加上其余的点。这样这两相邻的点就肯定不能满足都是好点。

对于第一种情况,特判即可。

下面讨论第二种情况:
那么对于一个点是好点的话,其相邻的点都一定不是好点。一个点不是好点,那么其相邻的点可能是好点,可能不是。
同时,需要保证最终的好点个数最多,在这个条件下点权之和最小。

这种情况下,可以考虑用树形dp。
dp[i, 0]:点 i 不是好点时,以点 i 为根的子树,最多的好点个数 和 最小的点权之和。
dp[i, 1]:点 i 是好点时,以点 i 为根的子树,最多的好点个数 和 最小的点权之和。

因为每个点要维护两个元素,所以可以用结构体定义:

struct node{
	int gcnt, sum;
}dp[N][2]; 

当一个点为好点时,其值为相邻节点的权值之和,为了使得点权之和最小,所以相邻的节点,也就是不是好点的点权值全都赋值为1。那么好点的权值便是其相邻节点的个数。

所以对于一个点是好点的话,dp[i, 1] 的好点个数初始为 1,点权和初始为相邻点的个数;累加上其每个子节点的坏点状态时的好点个数和点权和;
不是好点的话,dp[i, 0] 的好点个数初始为 0,点权和初始为 1。累加上其每个子节点的两个状态中较好状态的好点个数和点权和。

如何比较两个状态呢?
当 好点个数较多 或者 好点个数相同的情况下点权和较小,则说明状态较好。
为了便于比较,可以重载大于号:

bool operator > (node a, node b){
	if(a.gcnt != b.gcnt) return a.gcnt > b.gcnt;
	return a.sum < b.sum;
}

从树形图的任意一个点出发,便可以得出整棵树的最大好点个数和最小点权和:

void dfs1(int u, int fa)
{
	dp[u][1].gcnt = 1, dp[u][1].sum = e[u].size();
	dp[u][0].gcnt = 0, dp[u][0].sum = 1;
	
	for(auto tx:e[u])
	{
		if(tx == fa) continue;
		dfs1(tx, u);
		
		dp[u][1].gcnt += dp[tx][0].gcnt;
		dp[u][1].sum += dp[tx][0].sum;
		
		int better = (dp[tx][1] > dp[tx][0])?1:0;
		dp[u][0].gcnt += dp[tx][better].gcnt;
		dp[u][0].sum += dp[tx][better].sum;
	}
}

这道题还要求输出在这个最佳情况下,每个点的赋值情况。所以我们按照最佳的路径重新遍历一遍。

void dfs2(int u, int state, int fa)
{
	if(state) ans[u] = e[u].size();
	else ans[u] = 1;
	
	for(auto tx : e[u])
	{
		if(tx == fa) continue;
		
		if(state) dfs2(tx, 0, u);
		else{
			int better = (dp[tx][1] > dp[tx][0])?1:0;
			dfs2(tx, better, u);
		} 
	}
}

完整Code:

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

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'

map<int,int> mp;

const int N = 200010, mod = 1e9+7;
int T, n, m, k;
int a[N], ans[N];
vector<int> e[N];
struct node{
	int gcnt, sum;
}dp[N][2]; 

bool operator > (node a, node b){
	if(a.gcnt != b.gcnt) return a.gcnt > b.gcnt;
	return a.sum < b.sum;
}

void dfs1(int u, int fa)
{
	dp[u][1].gcnt = 1, dp[u][1].sum = e[u].size();
	dp[u][0].gcnt = 0, dp[u][0].sum = 1;
	
	for(auto tx:e[u])
	{
		if(tx == fa) continue;
		dfs1(tx, u);
		
		dp[u][1].gcnt += dp[tx][0].gcnt;
		dp[u][1].sum += dp[tx][0].sum;
		
		int better = (dp[tx][1] > dp[tx][0])?1:0;
		dp[u][0].gcnt += dp[tx][better].gcnt;
		dp[u][0].sum += dp[tx][better].sum;
	}
}

void dfs2(int u, int state, int fa)
{
	if(state) ans[u] = e[u].size();
	else ans[u] = 1;
	
	for(auto tx : e[u])
	{
		if(tx == fa) continue;
		
		if(state) dfs2(tx, 0, u);
		else{
			int better = (dp[tx][1] > dp[tx][0])?1:0;
			dfs2(tx, better, u);
		} 
	}
}

signed main(){
	Ios;
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int x,y;cin>>x>>y;
		e[x].pb(y), e[y].pb(x);
	}
	
	if(n==2){
		cout << 2 << " " << 2 <<endl << 1<<" "<<1<<endl;
		return 0;
	}
	
	dfs1(1, 0);
	
	int better = (dp[1][1]>dp[1][0])?1:0;
	cout << dp[1][better].gcnt << " " << dp[1][better].sum << endl;
	
	dfs2(1, better, 0);
	
	for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值