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题:思维题
问题分析
首先
如果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
继续深入分析
要使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题:树构题,思维题
问题分析
题目要求:判断是否能通过割树边,将完整的一棵树变成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;
}