2020牛客寒假算法基础集训营3

链接:https://ac.nowcoder.com/acm/contest/3004
A 牛牛的DRB迷宫I
已知一个地图,图上每个点有一个字母,D表示从这点只能向下走,R表示只能向右走,B表示可以向下或向下走,问从点(1,1)走向点(n,m)共有多少种走法
dp即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
int n,m;
ll dp[100][100];
char s[100][100];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>s[i]+1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			if(i==1&&j==1) dp[1][1]=1;
			if(s[i][j-1]!='D') dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
			if(s[i-1][j]!='R') dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
		}
	cout<<dp[n][m];
	return 0;
 } 

C 牛牛的数组越位
数组下标都是从0开始的,正常说不会有负数下标,但是有时候下标为负也不会报错,有时候就会是RE
因为数组都是线性存储的二维数组也是如此,假设开一个数组 a[10][20] ,对于每次调用 a[i][j]都会计算一下
i20+j 来找到这个元素在线性存储的位置,这个数组的元素的位置是数组首地址到之后1020个元素的位置
这一段区间被数组全覆盖,如果下标是非法的但是在这个区间内 就不是RE而是UB 如果也不在这个区间内就是RE了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010,mod=1e9+7;
int n,m,t,k;
int main()
{
	cin>>t;
	while(t--)
	{
		cin>>n>>m>>k;
		map<pair<int,int> ,int> mp;
		int flag=0;
		for(int i=0;i<k;i++)
		{
			int a,b,val;
			cin>>a>>b>>val;
			if(flag==2) continue;
			int x=a*m+b;
			int y=x%m;
			x/=m;
			mp[{x,y}]=val;
			if(x<n&&x>=0&&y<m&&y>=0)
			{
				if(x!=a||y!=b) flag=1; 
			}
			else flag=2;
		}
		
		if(!flag)
		{
			for(int i=0;i<n;i++)
			{
				for(int j=0;j<m;j++)
					cout<<mp[{i,j}]<<' ';
				cout<<endl;
			}
			puts("Accepted");
		}
		else if(flag==1)
		{
			for(int i=0;i<n;i++)
			{
				for(int j=0;j<m;j++)
					cout<<mp[{i,j}]<<' ';
				cout<<endl;
			}
			puts("Undefined Behaviour"); 
		}
		else puts("Runtime error");
	}
	return 0;
 } 

D牛牛与二叉树的数组存储
这个存储就是线段树的存储方式 i节点的父节点是 i/2,左孩子是i>>1,右孩子是i>>1|1
需要注意的是输出顺序是按照点的顺序输出的,不是按照下标的顺序,所以要开一个下标来记录每个点的位置

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=400010,mod=1e9+7;
int n,m,t,k;
int s[N],num[N];
int main()
{
	memset(s,-1,sizeof s);
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>s[i];
	int res=0;
	for(int i=1;i<=n;i++)
		if(s[i]!=-1) 
			res++,num[s[i]]=i;
	printf("The size of the tree is %d
",res);
	printf("Node %d is the root node of the tree
",s[1]);
	for(int i=1;i<=res;i++)
	{
		int j=num[i];
		printf("The father of node %d is %d, the left child is %d, and the right child is %d
",s[j],s[j/2],s[j*2],s[j*2+1]);
	}
	return 0;
 } 

F 牛牛的Link Power I
计算任意两个1之间距离和
这个有很多方法,我开了一个数组保存这个i与前面的1产生的距离和,同时记录最后一个遍历到的1的位置,还有1的数量为num ,那么到下一个1的位置,这个1与前面num个1会产生多少贡献呢,可以与上一个1的贡献作比较,记这个1与上一个1的距离为想,前面共有num个1每个1与当前1的距离都是 它与上一个1的距离加x,之前的1与上一个1产生的贡献已经计算过,每个1还会多贡献一个x,共有num个x,这样当前1的贡献就算好了,就可以继续向后算了。所有的贡献相加就可以了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010,mod=1e9+7;
int n,m,dp[N];
char s[N];
int main()
{
	cin>>n>>s;
	int j=-1,cnt=0,ans=0;
	for(int i=0;i<n;i++)
	{
		if(s[i]=='1')
		{
			if(j==-1)//前面没有1,不需要计算
				j=i,cnt++;
			else
			{
				dp[i]=(dp[j]+cnt*(i-j))%mod;
				ans=(ans+dp[i])%mod;
				cnt++;
				j=i; //记录当前1的位置,方便后面的1的计算
			 } 
		}
	} 
	cout<<ans;
	return 0;
 } 

G 牛牛的Link Power II
在上一题的基础上增加了单点修改,很明显是用线段树去做,询问的是整个字符串的的link,所以就不用询问函数了,只用输出tr[1].sum就可以了 ,那么怎么去维护呢,对于pushup操作,父区间的link等于左区间的link加右区间的link再加左右区间新产生的link。左右区间新产生的要怎么去算呢,假设左区间的1是点 i,右区间的1是点 j,对于任意 i 与 j,它俩的贡献= ri(i到左区间的右端点的距离 )+ lj( j到右区间的左端点的距离)+1,(线段树的左右区间不相邻会有距离1的空隙)。令 i 不变 j 任意,对ri+lj+1求和可以发现日是不变的,ri+1共加了cnt2个(右区间1的总个数),而对lj求和可以用一个变量来表示 lsum2(右区间区间的1到区间左端点的距离和),
所以对于某一个点i来说他的总贡献=(ri+1)cnt2+lsum2=ricnt2+cnt2+lsum2;
然后就要对任意的i求和了,每一个 i 的贡献都是上面那个式子,记cnt1是左区间1的个数,对上面的式子求和会发现 cnt2+lsum2是不变的共加了cnt1次 对任意ri求和也可以用一个变量rsum1来保存(左区间renyi1到区间右端点的距离和)所以求和后= rsum1cnt2+lsum2cnt1+cnt1*cnt2,这样就可以求得左右区间新产生的link了(因为(i,j)是无序的,这样就是结果了,先考虑右区间结果是一样的(这是我以为的…));
这样父区间的link就可以得到了,所以每个区间我们要记录四个值 sum(link),cnt(区间内点的数量),lsum(所有点到左端点的和),rsum(所有点到右端点的和)。(lsum和rsum的维护直接看代码吧)

#include<iostream>
using namespace std;
const int N=100010,mod=1e9+7;
typedef long long ll;
struct node
{
	int l,r;
	ll sum,cnt,lsum,rsum;
}tr[N*4];//线段树需要开4倍
int n,m;
char s[N];

void pushup(int rt)
{
	tr[rt].cnt=tr[rt<<1].cnt+tr[rt<<1|1].cnt;//维护cnt
	tr[rt].sum=(tr[rt<<1].sum+tr[rt<<1|1].sum+tr[rt<<1].rsum*tr[rt<<1|1].cnt+tr[rt<<1|1].lsum*tr[rt<<1].cnt+tr[rt<<1].cnt*tr[rt<<1|1].cnt)%mod;//维护sum
	tr[rt].lsum=(tr[rt<<1|1].lsum+tr[rt<<1].lsum+(tr[rt<<1].r-tr[rt<<1].l+1)*tr[rt<<1|1].cnt)%mod;//维护lsum
	tr[rt].rsum=(tr[rt<<1|1].rsum+tr[rt<<1].rsum+(tr[rt<<1|1].r-tr[rt<<1|1].l+1)*tr[rt<<1].cnt)%mod;//维护rsum
}
void build(int rt,int l,int r)
{
	if(l==r)
	{
		int a=s[l]-'0';
		tr[rt]={l,l,0,a,0,0};//sum,lsum,rsum都必须是0,
	}
	else
	{
		tr[rt]={l,r};
		int mid= l+r >> 1;
		build(rt<<1,l,mid);
		build(rt<<1|1,mid+1,r);
		pushup(rt);
	}
}
void updata(int rt,int x,int k)
{
	if(tr[rt].l==x&&tr[rt].r==x) 
	{
		tr[rt].cnt=k;//更改cnt后再pushup维护即可,叶子区间的sum,lsum,rsum一直都是0
	}
	else
	{
		int mid= tr[rt].l+tr[rt].r >> 1;
		if(x<=mid) updata(rt<<1,x,k);
		else updata(rt<<1|1,x,k);
		pushup(rt);  
	}
}
int main()
{
	cin>>n>>s+1>>m;
	build(1,1,n);
	cout<<tr[1].sum%mod<<endl;
	int q,pos;
	while(m--)
	{
		cin>>q>>pos;
		updata(1,pos,q%2);
		cout<<tr[1].sum%mod<<endl;
	}
	return 0;
}

H 牛牛的k合因子数
k合因子数的定义见题目
通过打表可以发现不超过n的合因子数只与完全平方数有关,不是完全平方数的数与上一个完全平方数一样
所以枚举所有不超过n的所以完全平方数,统计并记录所有合因子的数目,就可以了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010,mod=1e9+7;
int n,m,num[N];

bool st[N];
void init()//素筛
{
	for(int i=2;i*i<=N;i++)
		if(!st[i])
			for(int j=2*i;j<N;j+=i)
				st[j]=1;
}
int find(int x)//找x有有几个合因子
{
	int cnt=0;
	for(int i=1;i*i<=x;i++)
	{
		if(x%i==0) 
		{
			if(x/i==i)
			{
				if(st[i])
					cnt++;
			}
			else 
			{
				if(st[i]) cnt++;
				if(st[x/i]) cnt++;
			}
		}
	}
	return cnt;
}
int main()
{
	init();
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		num[find(i)]++;
	}
	while(m--)
	{
		int k;
		cin>>k;
		cout<<num[k]<<endl;
	}
	return 0;
 } 

I 牛牛的汉诺塔
汉诺塔问题是一个很经典的问题,这个题打表就可以做,还需要借助一个结论 n层汉诺塔所需要的总步数是2^n-1。打表之后会发现一个很特殊的规律,n每增加1,6个结果只会有有3个会增加,剩余三个数不变;
当n为奇数时 有a->c,c->b,b-a会增加,当n偶数时其他三个增加,而且借助那个结论可以知道每次增加2^(n-1),这三个变量会基本均分 2 ^(n-1) ,但是也不会真的均分不能整除3嘛,每次都是其中一个到两个变量会多1个,当n是奇数时 a->c会多一个;当n是偶数时 a->b和b->c都会多一个(增量取余3等2)
到此为止,它的变化规律就找到了,递推就可以了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1010,mod=1e9+7;
int n,m,t,k;
int s[N],num[N][N];
void Hanoi(int n,int a,int b,int c)//题目上的汉诺塔函数,python改成c就可以了
{
    if (n==1 )
    	num[a][c]++;//记录变量,这里的a,b,c并不是题目中的a,b,c,递归时会变顺序的
    else
    {
        Hanoi(n-1,a,c,b);
        num[a][c]++;
        Hanoi(n-1,b,a,c);
    }
}
ll a,b,c,d,e,f,sum;
int main()
{
	int n;
	cin>>n; 
	for(int i=1;i<=n;i++)
	{
		if(i&1)
		{
			ll res=pow(2,i-1);
			b+=(res/3+1);
			c+=(res/3);
			f=c;
		}
		else 
		{
			ll res=pow(2,i-1);
			a+=(res/3+1);
			d=a;
			e+=(res/3);
		}
	}
	printf("A->B:%lld
",a);
	printf("A->C:%lld
",b);
	printf("B->A:%lld
",c);
	printf("B->C:%lld
",d);
	printf("C->A:%lld
",e);
	printf("C->B:%lld
",f);
	printf("SUM:%lld",a+b+c+d+e+f);
	return 0;
 } 

其他几个题还没补。。。。。。

原文地址:https://www.cnblogs.com/neflibata/p/12871787.html