第三次训练赛

A - Kefa and Park
题目链接:https://vjudge.net/contest/345029#problem/A
题意:
有n个点的树编号1到n,已知每个点有没有猫,以1为根节点,从1出发,路上不能有连续m个猫,问有几个叶子结点可以到达
思路:
以连接表形势存图,叶子结点的判断是 e[i].size()==1 并且 该节点不是根节点 dfs即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
vector<int> mp[maxn];
int n,m,s[maxn];
int ans=0,mx;
void dfs(int i,int pre,int cnt)//pre是父亲节点 不能往回走
{
	if(cnt+s[i]>m) return ;
	else if(mp[i].size()==1&&i!=1) ans++;
	if(s[i]) cnt++;
	else cnt=0;
	for(int j=0;j<mp[i].size();j++)
		if(mp[i][j]!=pre)
			dfs(mp[i][j],i,cnt);
}
int main()
{
	int x,y;
	cin>>n>>m;
	memset(s,0,sizeof s);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&s[i]);//保存是否有猫
	}
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		mp[x].push_back(y);
		mp[y].push_back(x);
	}
	dfs(1,-1,0);
	cout<<ans;
	return 0;
}

D - Alex and a Rhombus
链接:https://vjudge.net/contest/345029#problem/D

#include <stdio.h>
main()
{
	int n,
	dp[200];
	dp[1]=1;
	scanf("%d",&n);
	for(int i=2;i<=n;i++)
	{
		//printf("%d ",dp[i-1]);
		dp[i]=dp[i-1]+i*2+(i-2)*2;
		}
	printf("%d",dp[n]);
}

E - Nick and Array
题目链接::https://vjudge.net/contest/345029#problem/E
已知一个数组,每次变换 ai:=−ai−1 问乘积最大的序列是什么(顺序可以乱)
思路: 先把所有数字全变为负数 如果n是奇数再把 最小的那个负数变为正数(-1不能变)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+100;

int main()
{
	int n,s[maxn],mn=0,xx,num0=0;
	//cout<<mn<<endl;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		scanf("%d",&s[i]);
		if(s[i]>=0) s[i]=-s[i]-1;
		if(s[i]<mn&&s[i]!=-1) mn=s[i],xx=i;
		if(s[i]==-1) num0++;
	}
	if(num0==n&&n&1) s[0]=0;
	//cout<<num0<<' ';
	if(n&1&&num0!=n) s[xx]=-s[xx]-1;
	for(int i=0;i<n;i++)
		printf("%d ",s[i]);
	return 0;
}

F - Valeriy and Deque
题目链接:https://vjudge.net/contest/345029#problem/F
题意:有一个队列,每次从队列头部拿出前两个元素,将比较大的那个放到队列头部,比较小的哪一个放到队列尾部,问你第i拿出的两个元素是谁
思路:用双端队列模拟之后会发现最大的那个元素达到头部之后就会开始循环,循环节长度为 n-1 ,只需求出最大的元素到达头部之前每次的元素就好,循环节的顺序就是双端队列里面的顺序.

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=3e5+100;
int n,num,s[maxn],st[maxn][2],cnt=0;

int main()
{
	deque<int> q;
	int mx=0;
	cin>>n>>num;
	for(int i=0;i<n;i++)
	{
		cin>>s[i];
		mx=max(mx,s[i]);
		q.push_back(s[i]);
	}
	int a,b;
	while(q[0]!=mx)
	{
		a=q.front();
		q.pop_front();
		b=q.front();
		q.pop_front();
		st[cnt][0]=a;
		st[cnt++][1]=b;
		if(a<b) swap(a,b);
		q.push_front(a);
		q.push_back(b);
	}
	ll x;
	while(num--)
	{
		cin>>x;
		if(x<=cnt) cout<<st[x-1][0]<<' '<<st[x-1][1]<<endl;
		else
			cout<<q[0]<<' '<<q[(x-cnt-1)%(n-1)+1]<<endl;
	}
	return 0;
}

G - Circle Metro
题目链接:https://vjudge.net/contest/345029#problem/G
问甲乙两人能否在同一火车相遇
思路:已知起点,求他俩之间相差的站数,如果是奇数两人会在第一次擦身而过(如果n也是奇数两人转一整圈后会再次相遇) 判断站数的一半会不会比两人坐的总站数小就可以了

#include <bits/stdc++.h>

main()
{
	int n,a,x,b,y;
	scanf("%d%d%d%d%d",&n,&a,&x,&b,&y);
	int len=((b-a)+n)%n;
	int len1=((x-a)+n)%n;
	int len2=((b-y)+n)%n;
	if(len&1&&(len+n)%2==0) len=len+n;
	if(len%2==0&&len/2<=min(len1,len2))
	  	printf("YES");
	else printf("NO");
}

H - Pairs
题目链接:
如果有两个数满足题目要求,那么在第一个区间会有两种情况,1 两个数正好是那两个数 , 2 有一个数是那两个数之一,再分情况分别讨论两个数就行

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e5+100;
int num[maxn],s[maxn][2],st[maxn];
int main()
{
	int n,m,x=-1,y=-1,cnt=0;
	cin>>n>>m;
	bool flag=0;
	for(int i=0;i<m;i++)
	{
		scanf("%d%d",&s[i][0],&s[i][1]);
		if(s[i][0]>s[i][1]) swap(s[i][0],s[i][1]);//s[i][0]小于s[i][1]这样含有两个数时一一对应就可以了
	}
	x=s[0][0],y=s[0][1];
	int num1=0,num2=0,num3=0; //num1含有两个数的区间数,num2含有第一个数的区间数,num3含有第二个数的区间数(num2,num3包括num1)
	memset(st,0,sizeof st);  
	for(int i=1;i<m;i++)
	{
		if(x==s[i][0]&&s[i][1]==y) num1++;
		if(s[i][0]==x||s[i][1]==x) num2++;
		if(s[i][1]==y||s[i][0]==y) num3++;
		
	}
	if(num1==m-1||m==1) flag=1;//全都含有两个数或只有一个区间
	else//分别模拟
	{
		memset(num,0,sizeof num); 
		for(int i=1;i<m;i++)//统计非num2区间出现所有数的次数,因为s[i][0]!=s[i][1]所以num[i]就是含i的区间数
			if(s[i][0]!=x&&s[i][1]!=x)//非num2
			{
				num[s[i][1]]++;
				num[s[i][0]]++;
			}
		int mx=0;
		for(int i=1;i<=n;i++)
			mx=max(num[i],mx);
		if(mx+num2==m-1)//第一个区间没统计所以是m-1
			flag=1;
		else//统计num3 同上
		{
			memset(num,0,sizeof num); 
			for(int i=1;i<m;i++)
				if(s[i][0]!=y&&s[i][1]!=y)
				{
					num[s[i][1]]++;
					num[s[i][0]]++;
				}
			int mx=0;
			for(int i=1;i<=n;i++)
				mx=max(num[i],mx);
			if(mx+num3==m-1)
				flag=1;
		}
	}
	if(flag) puts("YES");
	else puts("NO");	
	return 0;
}

I - Increasing by Modulo
题目链接:https://vjudge.net/contest/345029#problem/I
题意: 有n个数字,每次操作可以选择若干个数,把每个数变为 (si+1)%m,问 把这个序列变为非减 序列需要最少的操作数是多少
如果最少的操作数是x 那么x+1,x+n次统统是可以实现的 所以可以二分

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e5+100;
int n,m,s[maxn];
bool check(int x)
{
	int dp[maxn];//dp[i]表示将第i个数变为非递减序列最少的操作数,可以只用一个变量的
	memset(dp,0,sizeof dp);
	if(m-s[0]<=x) dp[0]=(m-s[0])%m;//一定要取余
	for(int i=1;i<n;i++)
	{
		int y=(s[i-1]+dp[i-1])%m; //操作后前一个数的值
		if(s[i]>y)
		{
			if((x+s[i])%m>=y&&(-s[i]+y+m)%m<=x) dp[i]=(-s[i]+y+m)%m;//如果不大于x次操作可以把s[i]变为s[i-1] 就变为s[i-1] 因为要使后边的序列尽可能的小
			//else return 0;
		}
		else if(s[i]<y)
		{
			if((y-s[i])%m<=x) dp[i]=(y-s[i])%m;//同上
			else return 0;//如果x次变换不能的话,就是x一定比那个最小值要小
		}
	}
		
	return 1;
}
int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++)
		cin>>s[i];
	int l=0,r=m*2;
	while(l<=r)
	{
		int mid =(l+r)/2;
		if(check(mid))
		{
			r=mid-1;
		}
		else 
			l=mid+1;
	}
	cout<<l<<endl;
	return 0;
 } 

J - Mr. Kitayuta, the Treasure Hunter
题目链接:https://vjudge.net/contest/345029#problem/J
有n个岛线性排列,已知每颗钻石的位置(全部在岛上),你从0点出发,第一次可以跳到k点,以后每次距离可以与上次一样也可以加一,减一,路过每个点的钻石你都可以得到,问最多可以得到多少克

很容易就会想到dp了 但是3w*3w的数组太大了,不现实,看了网上几篇题解都说dp[i][ j]
表示上一次跳 j 步到达i点的最大价值,还说 j 只用开到500就可以了 ,我觉得说错了 因为如果第一步的d 大于500 就会直接越界了 这种说法有问题,代码没问题d ! ! !

最开始想的dp[ i ][ j ] 中 j 直接表示上一次跳的距离,这样行不通,那可不可以委婉的表示,每次距离的变化值只有三个数 -1 0 1 但是用这三个数表示的话就太乱了 只知道你上次变化量,跳了了几步就不知道了,缺少参考值 其实我们有一个很好的参考值-> d

即: 用dp[ i ] [ j ] 表示跳到 i 点上步距离是 d+j 没毛病吧 那我们来看看 j 的范围有多大 就直接用他们的250 吧 如果到达dp[ i ][ 250 ] 那么一定是从dp[i][249] 过来的
那么最短的跳过的距离和是多少: d + (d+1)+(d+2)+ … +(d+249)+(d+250)
求和后为 d * 250+250 * 251 / 2 且d>0 所以已经大于三万了 可以满足题目要求了
为啥要开500 呢 因为有正有负 250*2 完全可以了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e4+10;
int dp[500][maxn],num[maxn]; //dp的i j 互换了~~

int main()
{
	memset(dp,-1,sizeof dp);//一定预处理为-1 那么不是-1 的点就是可以到达的 就可以区分开了 再以改点向后就可以了
	memset(num,0,sizeof num);
	int n,d,x,maxm=0;
	cin>>n>>d;
	for(int i=0;i<n;i++)
	{
		scanf("%d",&x);
		num[x]++;
	}
	int ans=num[d];
	dp[250][d]=num[d];//250-250 就是差值为0 了
	for(int j=d;j<maxn;j++)
		for(int i=1;i<500;i++)
			if(dp[i][j]!=-1)//
			{
				int l=i-250+d;//上一次跳的距离
				for(int k=-1;k<=1;k++)//分别是三种变化
				{ 
					if(j+l+k<1||j+l+k>maxn||l+k<1) continue;//l+k 是这次跳的距离 必须大于0
					dp[i+k][l+j+k]=max(dp[i+k][j+l+k],dp[i][j]+num[l+j+k]);
					ans=max(ans,dp[i+k][j+l+k]);//更新最大值
				}
			}
	cout<<ans;
	return 0;
 } 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章