Codeforces Global Round 13(CDE)题解

C题:思维题

C题链接

问题分析

循环分别判断每个点的价值,即因为某个点,导致的新增兔子数量

假设s[i]=5,其需要前者经过它四次才可使其变为1

若前者只经过 i 点 b[i] 次(b[i]<s[i]),则需要再有s[i]-b[i]-1只兔子经过即可
若前者经过它的次数少于5,则其会经过 [ i+2,i+5 ] 的所有点各一次
若前者经过它的次数大于等于5,则其会经过 [i+2,i+2] 的所有各一次,
且经过 i+1 点s[i]-b[i]-1次

完整代码如下

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

long long a[10100],b[10100],n,ans;

int main() {
    
    
	int T;
	cin>>T;
	while(T--) {
    
    
		scanf("%lld",&n);
		for(int i=1; i<=n; i++)scanf("%lld",&a[i]);
		memset(b,0,sizeof(b));
		ans=0;
        
        for(int i=1;i<=n;i++){
    
    
        	if(a[i]>=b[i]+1)ans+=a[i]-b[i]-1;
        	
        	for(int j=i+2;j<=i+a[i]&&j<=n;j++)b[j]+=1;
			if(a[i]<b[i]+1)b[i+1]+=b[i]+1-a[i];
		
		}
        cout<<ans<<endl;
	}
	return 0;
}

D题:思维题

D题链接

问题分析

首先
如果u>v,不可能有路径
如果u==v,不需要路径,之间到达

当u<v,因为涉及位运算
将u,v均转化成二进制下分析
x->x+y,x&y==y表示:只有x的第i位有1时,y的第i位能为1,且如何时候都可为0

例如:
x=13=1101,有1位分别为第1,3,4位
则y可取1000,100,1,1100,1001,101,1101

扫描二维码关注公众号,回复: 13121310 查看本文章

继续深入分析

要使u最后等于v

我们循环从 i 第1位到第30位判断两个数的前 i 位是否可以完全相同

情形1: 若前 i 位1的数量相同,则u一定能过加上一些数,使得u的前 i 位与v的前 i 位一致,且不影响高位
例如:
u=01,v=10,+1
u=0101,v=1010,+1+4

情形2: 若前 i 位1的数量u比v多,则u一定能过加上一些数,使得u的前 i 位与v的前 i 位一致,但会影响高位
例如:
u=11,v=10,+2+1,此时u的前两位为10,与v一致,但第三位不一致,不过会在后面的循环判断前三位是否可以完全相同

情形3: 若前 i 位1的数量u比v少,则不可能使得一致
例如:
u=0,v=1
u=10,v=11

综上分析
假设u=011,v=110
分析前1位,符合情形2,前1位相同,但第2位欠了一个1
分析前2位,符合情形2,前2位相同,但第3位欠了一个1
分析前3位,符合情形1,前3位相同,欠的一个1被还清了

完整代码如下

#include<iostream>
#include<cstdio>
using namespace std;

int a[50],b[50];

int check(int u,int v) {
    
    
	if(u>v)return 0;
	if(u==v)return 1;
	for(int i=0; i<=29; i++) {
    
    
		if(1<<i&u)a[i]=1;
		else a[i]=0;
		a[i]=a[i-1]+a[i];
		if(1<<i&v)b[i]=1;
		else b[i]=0;
		b[i]=b[i-1]+b[i];
	}
	for(int i=0; i<=29; i++) {
    
    
		if(a[i]<b[i])return 0;
	}
	return 1;
}

int main() {
    
    
	int T,u,v;
	scanf("%d",&T);
	while(T--) {
    
    
		scanf("%d%d",&u,&v);
		if(check(u,v))cout<<"YES"<<endl;
		else cout<<"NO"<<endl;
	}
	return 0;
}

E题:树构题,思维题

E题链接

问题分析

题目要求:判断是否能通过割树边,将完整的一棵树变成n个结点树为1的树,且保证割的过程中的每棵树结点数始终为斐波那契数列的某一项

分析:若当前该树结点为fib[k],要满足题目要求,则其只能被割成两棵结点树分别为fib[k-1]和fib[k-2]的树,易知这样的割边可能不止一条,但可以通过证明无论割哪条,最后的结果不会变。

朴素算法:每次都遍历树,找到这样的割边,删除边,并将树分成两部分。因为树的形态变了,所以还要重新计算树的各个位置的结点数

时间复杂度计算:
本以为这样的算法是O(n2)的,所以不敢写,但事实上其的复杂度不是O(n2)

假设有n=13,通过该朴素算法

在这里插入图片描述

可以看出,每层都是O(n),有多少层呢?取决于n是第几项?

斐波那契数列增长率:( 1 + 5 2 \frac{1+\sqrt{5}}{2} 21+5 )n

则n大概在logn层
总时间复杂度为O(nlogn),没有超时

则朴素算法暴力即可

完整代码如下(链式前向星存图)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N=2e5+100;
struct Edge {
    
    
	int u,v,next;
} e[N*2];
int vex[N],k=-1,vis[N*2],n;
int fib[100],size[N];

void add(int u,int v) {
    
    //存边 
	k++;
	e[k].u=u;
	e[k].v=v;
	e[k].next=vex[u];
	vex[u]=k;
}

void init_fib(){
    
    //预处理斐波那契数列 
	fib[1]=fib[2]=1;
	for(int i=3;i<=35;i++)fib[i]=fib[i-1]+fib[i-2];
}
void getSize(int u,int fa) {
    
    //计算树的结点数 
	size[u]=1;
	for(int i=vex[u]; i!=-1; i=e[i].next) {
    
    
		int v=e[i].v;
		if(v==fa)continue;
		if(vis[i])continue;
		getSize(v,u);
		size[u]+=size[v];
	}
}
void cutEdge(int u,int fa,int k,int &cutu,int &cutv,int &numu,int &numv){
    
    //找割边,并割  
	for(int i=vex[u];i!=-1;i=e[i].next){
    
    
		int v=e[i].v;
		if(cutu)return;//找到割边了,婷婷!!! 
		if(v==fa)continue;
		if(vis[i])continue; 
		if(size[v]==fib[k-1]||size[v]==fib[k-2]){
    
    
			vis[i]=1;
			vis[i^1]=1;
			cutu=u;
			cutv=v;
			numu=(size[v]==fib[k-1])?k-2:k-1;
			numv=(size[v]==fib[k-1])?k-1:k-2;
			break;
		}
		cutEdge(v,u,k,cutu,cutv,numu,numv); 
	}
}

void divideTree(int u,int k){
    
    //以u结点为根的点是斐波那契数列第k项
    if(k<=2)return;
    
	getSize(u,0);
	int cutu=0,cutv=0,numu=0,numv=0;
	cutEdge(u,0,k,cutu,cutv,numu,numv);
	
	if(cutu==0){
    
    //没有找到割边,over 
		cout<<"NO";
		exit(0);
	}
	
	divideTree(cutu,numu);//分成两棵树,分别割 
	divideTree(cutv,numv);
}

int main() {
    
    
	int u,v;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)vex[i]=-1;
	for(int i=1; i<n; i++) {
    
    
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
    init_fib();
    
    int t=lower_bound(fib+1,fib+30+1,n)-fib;
    if(fib[t]!=n){
    
    
    	cout<<"NO";
    	exit(0);
	}
	
	divideTree(1,t);
	
	cout<<"YES";
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43602607/article/details/114283214