第三次訓練賽

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;
 } 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章