zoukankan      html  css  js  c++  java
  • 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;
     } 
    

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

  • 相关阅读:
    公司初创期使用 PHP,为什么很多公司都会慢慢转型到 JAVA
    Firefox 如何对发送的参数进行调试
    Spring security CSRF 跨域访问限制问题
    IntelliJ IDEA 的 Maven 如何设置自动下载源代码和文档
    Spring 的 WebSecurityConfigurerAdapter 过滤器
    Java NIO Path 接口
    Joda-Time – 可用的时区列表
    JWT 如何解码和获得令牌失效的日期
    MySQL索引相关操作
    详细了解INNODB_TRX、INNODB_LOCKs、INNODB_LOCK_waits、PROCESSLIST表
  • 原文地址:https://www.cnblogs.com/neflibata/p/12871787.html
Copyright © 2011-2022 走看看