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

A . honoka和格点三角形
链接:https://ac.nowcoder.com/acm/contest/3002/A

honoka最近在研究三角形计数问题。
她认为,满足以下三个条件的三角形是“好三角形”。
1.三角形的三个顶点均为格点,即横坐标和纵坐标均为整数。
2.三角形的面积为 。
3.三角形至少有一条边和 轴或 轴平行。
honoka想知道,在平面中选取一个大小为 的矩形格点阵,可以找到多少个不同的“好三角形”?由于答案可能过大,请对 取模。
输入描述:
两个正整数和( 2\ ≤n,m≤10^9)(2 ≤n,m≤10 ^9)
输出描述:
面积为1的格点三角形的数量,对 10^9+7 取模的结果。

面积为二的三角形 只能是 底1高2 或者底2高1 分别以横纵坐标轴标轴方向为底 计算共多少情况 需要注意的是 直角三角形会有重复,我计算了底边横向的所有情况
底边纵向的删去所有直角三角形 以底边横向为例
加入底边长 1 那么在一条横线上 可以有(m-1)个底边每个底边朝一个方向可以有多少个顶点呢,是m个。只要到底边距离为2就可以了,已经有一条边平行坐标轴了完全符合条件。 而每条线的三角形都有上下两个方向所以(但边缘的两个都只有一个)所以 再乘(2*n-2) 然后该底边是2的情况 只需将(m-1)改为(m-2)就行了
(长度为m的边上只能有(m-2)个长度为2的子边)
计算底边纵向时 和刚才死的分析思路是一样 就是要减去直角三角形 减多少个呢
以每个底边为例 底边的两端点对应的顶点所构成的就是直角三角形了,每个底边减二就可以了 注意別爆 ll
具体看代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010,mod=1e9+7;
ll cnt,n,m;
int main()
{
	while(cin>>n>>m)
	{
		ll ans=((((n-2)*m)%mod)*(2*m-2))%mod;
		ans=(ans+((n-1)*m)%mod*(2*m-4)%mod)%mod	;
		ans=(ans+((m-2)*(n-2))%mod*(2*n-2)%mod)%mod;
		ans=(ans+((m-1)*(n-2))%mod*(2*n-4)%mod)%mod;
		cout<<ans<<endl;	
	}
	return 0;
}

B 题 求期望就不说了 C没做(应该是最难的一道了)
D 排序后就知道缺那个数了
E. 求x的因子个数 只用遍历到 sqrt(x)就行了 所以最多也就是1e6的级别 不会超时 所以一直迭代就好了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010,mod=1e9+7;
int d[8][2]={1,2,2,1,2,-1,1,-2,-1,-2,-2,-1,-1,2,-2,1}; 
ll a,b,n,m;
int f(ll x)
{
	int cnt=0;
	for(ll i=1;i*i<=x;i++)
		if(x%i==0)
		{
			if(i*i==x)
				cnt++;
			else cnt+=2;
		}
	return cnt; 
}
int main()
{
	cin>>n;
	for(int i=1;i<100010;i++)
	{
		int x=f(n);
		if(x==2)
		{
			cout<<i<<endl;
			break;
		}
		else n=x;
	}
}

F 求树上路径最多只经过一个黑点的数量 所以黑点可能出现在端点或者中间
如果出现在端点 那么路径上其他点只能是白色 所以路径的数量就是黑点周围的白点的数量(与其他黑色节点之间的所有白节点)
如果出想在中间 那么白色节点就要经过这个黑节点与其他白节点相连(起点与终点必须位于黑点的不同的连通分支中) 求每个连通分支的与其他黑点之间的白点数量
再把任意两个分支的数量相乘就可以了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200010,mod=1e9+7;
int  n,h[N],e[N],ne[N],idx;
char s[N];
vector<int> ve; 
void add(int a,int b)
{
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}
ll ans,sum[N];
int  dfs(int x,int pre)
{
	int res=1;
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int y=e[i];
		if(s[y]=='B'||y==pre) continue;//pre 父节点
		if(s[x]=='B')
		{
			ve.push_back(dfs(y,x));//每个连通分支的数量分别存储
		}
		else res+=dfs(y,x);
	}
	return res;
}
int main()
{
	memset(h,-1,sizeof h);
	cin>>n;
	cin>>s+1;
	for(int i=1;i<n;i++)
	{
		int a,b;
		cin>>a>>b;
		add(a,b);
		add(b,a);
	}
	for(int i=1;i<=n;i++)
		if(s[i]=='B')
		{
			ve.clear();
			ve.push_back(0);//千缀合的下标最好从1开始 所以预存一个0不影响结果
			dfs(i,-1);
			for(int j=1;j<ve.size();j++)//求前缀和 方便求任意两个数的乘积
				sum[j]=sum[j-1]+ve[j];
			for(int j=2;j<ve.size();j++)
				ans+=ve[j]*sum[j-1];//黑点在中间
			ans+=sum[ve.size()-1];//黑点在端点 所有分支的总合
		}
	cout<<ans;
}

G 求含k个相同字母的最小子串长度
这种求最值的问题很容易就会想到二分了,那怎么二分呢
首先肯定是需要二分长度了 那么怎么快速的判断是否合适呢
只要子串长度大于等于k个某字母的起始区间长度,那这个长度就是合适的
首先预处理字符串 因为只有26个字母,所以完全可以将每个字母出现的位置存下来
而且同一字母 位置的数组内是单增的 只需要遍历看 判断长度是否合适就行了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200010,mod=1e9+7;
int d[8][2]={1,2,2,1,2,-1,1,-2,-1,-2,-2,-1,-1,2,-2,1}; 
ll a,b,n,k;
char s[N];
vector<int> ve[30];
bool check(int x)
{
	for(int i=0;i<26;i++)
	{
		if(ve[i].size()>=k)
			for(int j=0;j+k-1<ve[i].size();j++)
				if(ve[i][j+k-1]-ve[i][j]<=x)
					return 1;
	}
	return 0;
}
int main()
{
	cin>>n>>k;
	cin>>s;
	for(int i=0;i<n;i++)
		ve[s[i]-'a'].push_back(i);
	int l=0,r=n,mid;
	while(l<r)
	{
		mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	if(l>=n) cout<<-1;
	else cout<<l+1; 二分的是不满足这个性质的最大长度
}

H.这道题还是可以二分,也是二分长度
把一段子串变成一样字符的最小操作=min(0的数量,1的数量);
怎么求一段字串的0和1的数量 对整段字符串求前缀和 然后就可以求区间和了,一段子串的区间和就是 1的数量了(没有对0计数)0的数量就是区间长度-区间和

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200010,mod=1e9+7;
int d[8][2]={1,2,2,1,2,-1,1,-2,-1,-2,-2,-1,-1,2,-2,1}; 
ll a,b,n,k;
char s[N];
int sum[N];
bool check(int x)
{
	for(int i=1;i+x-1<=n;i++)
	{
		int res=sum[i+x-1]-sum[i-1]; 
		if(min(res,x-res)<=k)
			return 1;
	}
	return 0;
}
int main()
{
	cin>>n>>k;
	cin>>s+1;
	for(int i=1;i<=n;i++)
		if(s[i]=='1') sum[i]=sum[i-1]+1;
		else sum[i]=sum[i-1];
	int l=0,r=n,mid;
	while(l<r)
	{
		mid=(l+r+1)>>1;//不要忘了加一
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	cout<<l;
}

I . 不同的单词有不同的分数,每个字母只能用一次,问最大分数是多少
这题非常适合dp,不管a,b,c的大小关系如何 每次dp去最大值就好,亏我想了那么种情况两个多小时也没做对,我太菜了。dp得好好复习复习了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=300010,mod=1e9+7;
string str;
ll a,b,c,n;
ll ans,dp[N];
int main()
{
	str.push_back('0');
	string s;
	cin>>n>>a>>b>>c>>s;
	str+=s;
	for(int i=1;i<=n;i++)
	{
		dp[i]=dp[i-1];//先更新再取最值
		if(i>=3&&str.substr(i-3,4)=="nico")
		{
			dp[i]=max(dp[i],dp[i-3]+a);
		}
		if(i>=5&&str.substr(i-5,6)=="niconi")
			dp[i]=max(dp[i],dp[i-5]+b);
		if(i>=9&&str.substr(i-9,10)=="niconiconi")
			dp[i]=max(dp[i],dp[i-9]+c);
	}
	cout<<dp[n];
	return 0;	
}
posted @ 2020-02-05 18:42  Neflidata  阅读(2)  评论(0编辑  收藏  举报