Codeforces Round #774 (Div. 2)

 A. Square Counting

题目链接:Problem - A - Codeforces

样例输入: 

4
7 0
1 1
2 12
3 12

样例输出:

0
1
3
1

题意:给定一个n,代表有n+1个数,每个数都是小于n的非负整数或者是n^2,现在我们知道n和这n+1个数的和,问我们有多少个数是n^2。

分析:容易发现,n+1个数全部都是n-1那么和才是n^2-1,那么就能够得到和中有几个n^2,那么原数组中就会有几个数是n^2

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		long long n;
		long long s;
		scanf("%lld%lld",&n,&s);
		printf("%lld\n",s/(n*n));
	}
	return 0;
}

B. Quality vs Quantity

题目链接:Problem - B - Codeforces

题意:给你n个点,每个点有一个权值,我们可以对每个点进行染色,可以染成红色或者蓝色或者不染,问是否存在一种方案使得染色数少的一方的权值和大。

分析:直接先对点按照权值进行从小到大排序,我们一开始先让一方取两个权值最小的点,另一方取一个权值最大的点,然后双方每轮操作各取一个点,点数多的一方尽可能取点权小的点,而点数少的一方尽可能取点权大的点,在过程中记录是否有满足题意的方案。

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
int a[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		sort(a+1,a+n+1);
		int l=2,r=n;
		long long ll=a[1]+a[2],rr=a[n];
		bool flag=false;
		while(l<r)
		{
			if(ll<rr)
			{
				flag=true;
				break;
			}
			ll+=a[++l];
			rr+=a[--r];
		}
		if(flag) puts("YES");
		else puts("NO");
	}
	return 0; 
}

C. Factorials and Powers of Two

题目链接:Problem - C - Codeforces

 样例输入:

4
7
11
240
17179869184

样例输出:

2
3
4
1

题意:给你一个数,问是否能表示成若干个互不相同的特殊数的加和。一个数是特殊数当且仅当这个数是2的幂次或者是一个数的阶乘。

分析:看一眼数据范围,发现这个数是小于10^12,也就是小于16!,既然每个阶乘最多用一次,那么我们就可以枚举用了哪些数的阶乘,然后剩下的数用2的幂次来表示即可,也就是看一下剩下的数的二进制中有多少个1,那么就需要用多少个2进制数来表示。按照这个思路直接进行状态枚举,然后在过程中记录最小值即可。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
long long f[N];
int main()
{
	int T;
	cin>>T;
	f[0]=1;
	for(int i=1;i<=16;i++)
		f[i]=f[i-1]*i;
	while(T--)
	{
		long long n;
		scanf("%lld",&n);
		int ans=0x3f3f3f3f;
		for(int i=0;i<(1<<17);i++)
		{
			if((i&1)&&(i>>1&1)) continue;
			long long tn=0;
			int tans=0;
			for(int j=0;j<17;j++)
			{
				if(i>>j&1) tn+=f[j],tans++;
				if(tn>n) break;
			}
			if(tn>n) continue;
			tn=n-tn;
			while(tn) tn-=(tn&-tn),tans++;
			ans=min(ans,tans);
		}
		printf("%d\n",ans);
	}
	return 0;
}

D. Weight the Tree

题目链接:Problem - D - Codeforces

样例输入: 

9
3 4
7 6
2 1
8 3
5 6
1 8
8 6
9 6

样例输出:

6 11
1 1 1 1 1 1 1 3 1 

题意:给定一棵n个结点的树,每个结点我们可以为其赋一个点权,一个结点是好节点当且仅当这个点的点权等于所有与其相邻的结点的点权和,问最多有多少个好节点,在好节点数量最多的前提下使得总结点权值尽可能小,最后需要输出权值方案。

分析:这道题是一道树形DP,类似于没有上司的舞会。

f1[i][0/1]表示以第i个点为根的子树中第i个结点有/无贡献时的有贡献点数

f2[i][0/1]表示以第i个点为根的子树中第i个结点有/无贡献时的最小权值 

容易发现,除了只有两个结点的情况,否则不可能有两个好节点是相邻的,这是显然的,所以我们就随机拟定一个结点为根节点,然后进行dp

假如当前结点是好节点,那么该节点的子节点一定不是好节点

假如当前结点不是好节点,那么该节点的子节点可能是好节点也可能不是好节点,这取决于子节点在两种情况下的贡献度,其中有贡献结点数目是第一优先级,当第一优先级相同时我们开始比较权值大小,优先选择总权值小的结点作为子节点。

在找哪些结点是好节点时的思路也是一样的,直接递归去寻找即可。

细节见代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=1e6+10;
int h[N],e[N],ne[N],idx;
long long f1[N][2],f2[N][2],cnt[N];
//f1[i][0/1]表示以第i个点为根的子树中第i个结点有/无贡献时的有贡献点数
//f2[i][0/1]表示以第i个点为根的子树中第i个结点有/无贡献时的最小权值 
bool vis[N];//vis[i]标记最优解中第i个点是否有贡献 
void add(int x,int y)
{
	e[idx]=y;
	ne[idx]=h[x];
	h[x]=idx++;
}
void dfs(int x,int fa)
{
	f1[x][0]=0;f1[x][1]=1;
	f2[x][0]=1;f2[x][1]=cnt[x];
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==fa) continue;
		dfs(j,x);
		if(f1[j][0]>f1[j][1])//当前结点无贡献时优先选择子节点贡献点数多的 
		{
			f1[x][0]+=f1[j][0];
			f2[x][0]+=f2[j][0];
		}
		else if(f1[j][0]<f1[j][1])//当前结点无贡献时优先选择子节点贡献点数多的 
		{
			f1[x][0]+=f1[j][1];
			f2[x][0]+=f2[j][1];
		}
		else//当子节点贡献点数一样多的时候优先选择总权值小的 
		{
			f1[x][0]+=f1[j][0];
			f2[x][0]+=min(f2[j][0],f2[j][1]);
		}
		//当前结点有贡献时子节点一定没贡献 
		f1[x][1]+=f1[j][0];
		f2[x][1]+=f2[j][0];
	}
}
void show(int x,int fa,int v)
{
	//将当前点标记为有贡献 
	if(v==1)
		vis[x]=true;
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==fa) continue;
		if(v==1)//如果当前结点有贡献,那么子节点一定没贡献 
			show(j,x,0);
		else
		{
			if(f1[j][0]>f1[j][1])//优先选择子节点贡献结点数目多的子节点 
				show(j,x,0);
			else if(f1[j][0]<f1[j][1])//优先选择子节点贡献结点数目多的子节点 
				show(j,x,1);
			else if(f2[j][0]<f2[j][1])//其次选择权值小的子节点 
				show(j,x,0);
			else
				show(j,x,1);
		}
	}
}
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) h[i]=-1;
	int u,v;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
		cnt[v]++;cnt[u]++;
	}
	if(n==2)
	{
		printf("2 2\n1 1");
		return 0;
	}
	dfs(1,-1); 
	if(f1[1][0]>f1[1][1])
	{
		printf("%lld %lld\n",f1[1][0],f2[1][0]);
		show(1,-1,0);
	}
	else if(f1[1][0]<f1[1][1])
	{
		printf("%lld %lld\n",f1[1][1],f2[1][1]);
		show(1,-1,1);
	}
	else if(f2[1][0]<f2[1][1])
	{
		printf("%lld %lld\n",f1[1][0],f2[1][0]);
		show(1,-1,0);
	}
	else
	{
		printf("%lld %lld\n",f1[1][1],f2[1][1]);
		show(1,-1,1);
	}
	for(int i=1;i<=n;i++)
		if(vis[i]) printf("%d ",cnt[i]);
		else printf("1 ");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值