1. 程式人生 > 其它 >周師2021年寒假半月賽1

周師2021年寒假半月賽1

技術標籤:從零開始的ACM生活演算法動態規劃貪心比賽題解

補題地址

http://acm.zknu.edu.cn/contest.php?cid=1118

Problem A 猜密碼

思路

把答案分為以零結尾的數字和不以零結尾的數字兩種,開一個記錄答案的陣列ans[i][0/1],表示i位數以0或不以0結尾的方案數。
顯然ans[1][0]=1,ans[1][1]=9。
如果以0結尾,該i位數字串的下一位可以是任何數字。如果以1結尾,該i位數字串的下一位可以是除了0以外的任何數字。
易知ans[i][0]=ans[i-1][1],ans[i][1]=(ans[i][0]+ans[i][1])*9。
求解過程中要對1e9+7取餘以防溢位。

程式碼

#include <stdio.h>
const int p=1e9+7;
int n,T,a[10000001],ans[1000001][2];
int main()
{
	while (scanf("%d",&a[++T])!=EOF)
		if (a[T]>n) n=a[T];
	T--;
	ans[1][0]=1;
	ans[1][1]=9;
	for (int i=2;i<=n;++i) 
	{
		ans[i][0]=ans[i-1][1];
		ans[i][1]=(ans[i-1][0]*9ll%p+ans[i-1][1]*9ll%p)%p;
} //9ll是longlong型別的九,相當於進行了一次強制型別轉化。 for (int i=1;i<=T;++i) printf("%d\n",(ans[a[i]][0]+ans[a[i]][1])%p); return 0; }

Problem B 打彈珠

思路

N個彈珠完全相同,且預設發生完全彈性碰撞,碰撞後兩彈珠的速度和方向均交換。我們並不對彈珠們的標號與末位置的對應關係加以要求,所以可以視為彈珠之間不會發生碰撞。
計算所有彈珠的末狀態,按要求排序後輸出。

程式碼

#include <stdio.h>
#include <algorithm>
using namespace std; struct asdf{ int x,y,z; }a[101]; bool operator < (asdf a,asdf b) { if (a.x==b.x) if (a.y==b.y) return a.z<b.z; else return a.y<b.y; return a.x<b.x; } int main() { int n,t,vx,vy,vz; scanf("%d%d",&n,&t); for (int i=1;i<=n;++i) { scanf("%d%d%d%d%d%d",&a[i].x,&a[i].y,&a[i].z,&vx,&vy,&vz); a[i].x+=vx*t; a[i].y+=vy*t; a[i].z+=vz*t; } sort(a+1,a+1+n); for (int i=1;i<=n;++i) printf("%d %d %d\n",a[i].x,a[i].y,a[i].z); }

Problem C 積水坑

思路

每一列對答案的貢獻為min(它左邊最大數,它右邊最大數)-它自己的高度。
比如下圖第三列的答案,為min(3,2)-1=2-1=1。
在這裡插入圖片描述

求出每一列的答案我們就可以累加求出總的答案。
另外開兩個陣列,正著遍歷即可求出每個元素前面最大的元素, l[i]=max(a[i-1],l[i-1]),同理我們逆序遍歷即可求出每個元素後面最大的元素r[i]=max(a[i+1],r[i+1]),最後統計答案。
我的程式碼中的l和r陣列包含了自己,顯然可以發現對最後的答案並沒有影響。

程式碼

#include <stdio.h>
#include <string.h>
#define min(a,b) (a>b?b:a) 
int n,a[1005],l[1005],r[1005];
int qwq()
{
	memset(l,0,sizeof(l));
	memset(r,0,sizeof(r));
	memset(a,0,sizeof(a));
	int ans=0;
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
	{
		scanf("%d",&a[i]);
		if (a[i]>l[i-1]) l[i]=a[i];
		else l[i]=l[i-1];
	}
	for (int i=n;i>=1;--i)
	{
		if (a[i]>r[i+1]) r[i]=a[i];
		else r[i]=r[i+1];
		if (min(l[i],r[i])-a[i]>0) ans+=min(l[i],r[i])-a[i];
	}
	return ans;
}
int main()
{
	int T;
	for (scanf("%d",&T);T--;) printf("%d\n",qwq());
	return 0;
}

Problem D 計算題

思路

題目背景中說道:冰茶几無法正確解決任何一個問題。
所以對於每組資料輸出0。

程式碼

#include <stdio.h>
int main()
{
	int T;
	for (scanf("%d",&T);T--;) printf("0\n");
}

Problem E 假期計劃

思路

將所有活動按結束時間升序排序,遍歷所有活動,如果可以參加活動i,則必然參加,ans++。輸出ans。

下面對於正確性進行感性認知證明。
排序後,我們遍歷整個陣列,此時對於可以被選擇的兩個活動(即發生時間與其他已經選擇了的活動沒有交集),它們的發生時間必然為以下三種情況:

  1. 活動一開始、活動一結束、活動二開始、活動二結束。
  2. 活動一開始、活動二開始、活動一結束、活動二結束。
  3. 活動二開始、活動一開始、活動一結束、活動二結束。

對於第一種情況,兩個活動沒有交集,不影響彼此是否被選擇。
對於第二種情況,由於兩個活動都可以被選擇,所以活動二開始之前一定沒有活動正在舉行,我們可以忽略掉活動一舉行到活動二舉行這一段時間,變為第三種情況。
對於第三種情況,選擇活動二必然更優(對其他活動的選擇影響更小)。
綜上,我們的選擇策略沒有問題。

程式碼

#include <stdio.h>
#include <algorithm>
struct asdf{
	int a,b;
}a[1000001];
int n,x,ans;
int cmp(asdf a,asdf b)
{
	return a.b<b.b;
}
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;++i) scanf("%d %d",&a[i].a,&a[i].b);
	std::sort(a+1,a+1+n,cmp);
	for (int i=1;i<=n;++i)
		if (x<a[i].a) ans++,x=a[i].b;
	printf("%d",ans);
}

Problem F 輸水管道

思路

資料範圍較小,我們可以用二維陣列儲存整張地圖,進行dfs。
因為這個dfs很裸我不知道該怎麼bb,可以看程式碼把資料代入手玩一下。

程式碼

#include <stdio.h>
#define max(a,b) (a>b?a:b)
int n,m,q,a[1005][1005],v[1001][1001][2],ans[5001],t,maxx;
void dfs(int x,int y,int z)
{
	if (a[x][y]==1)
	{
		v[x][y][0]=v[x][y][1]=t;
		if (x>1&&!v[x-1][y][0]) dfs(x-1,y,0);
		if (x<n&&!v[x+1][y][0]) dfs(x+1,y,0);
		if (y>1&&!v[x][y-1][1]) dfs(x,y-1,1);
		if (y<m&&!v[x][y+1][1]) dfs(x,y+1,1);
	}
	else
	{
		v[x][y][z]=t;
		if (z==0)
		{
			if (x>1&&!v[x-1][y][0]) dfs(x-1,y,0);
			if (x<n&&!v[x+1][y][0]) dfs(x+1,y,0);	
		}
		else 
		{
			if (y>1&&!v[x][y-1][1]) dfs(x,y-1,1);
			if (y<m&&!v[x][y+1][1]) dfs(x,y+1,1);
		}
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for (int i=1;i<=q;++i)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x][y]=1;
	}
	for (int i=1;i<=n;++i)
		for (int j=1;j<=m;++j)
			if (a[i][j]==1)
			{
				if (v[i][j][0]) ans[v[i][j][0]]++;
				else ans[++t]=1,dfs(i,j,2);
			}
	for (int i=1;i<=t;++i) maxx=max(maxx,ans[i]);
	printf("%d",maxx);
}

Problem G 睡眠時間

思路

睡眠時間小於一天,所以年月日可以忽略掉(
直接做差就好。
這個題wa了應該去面壁。

程式碼

#include <stdio.h>
int main()
{
	int n,ay,am,ad,ah,amin,as,by,bm,bd,bh,bmin,bs;
	for (scanf("%d",&n);n--;)
	{
		scanf("%d%d%d%d%d%d%d%d%d%d%d%d",&ay,&am,&ad,&ah,&amin,&as,&by,&bm,&bd,&bh,&bmin,&bs);
		if (bs<as) bs=bs+60-as,bmin--;
		else bs-=as;
		if (bmin<amin) bmin=bmin+60-amin,bh--;
		else bmin-=amin;
		if (bh<ah) bh=bh+24-ah,bd--;
		else bh-=ah;
		printf("%d %d %d\n",bh,bmin,bs);
	}
}

Problem H 跳荷葉

思路

儘可能跳到跳躍範圍內最遠的荷葉上。
將荷葉按距離岸邊從小到大排序,排好序後從第一片荷葉開始遍歷所有荷葉,記錄青蛙跳了ans步時距離岸邊的最大距離m。如果青蛙無法不經過當前荷葉就跳到下一片荷葉,ans++,青蛙跳到當前荷葉上。如果青蛙無法跳到當前荷葉上,輸出無解結束程式。
最後判斷青蛙能否跳上對岸,若能,輸出ans+1。

程式碼

#include <stdio.h>
#include <algorithm>
using namespace std;
int n,a[100001],x,m,ans;
int main()
{
	scanf("%d%d",&x,&n);
	n++;
	for (int i=1;i<=n;++i) scanf("%d",&a[i]);
	sort(a+1,a+n);
	for (int i=1;i<n;++i)
	{
		if (a[i]>m+x) return printf("-1\n"),0;
		if (a[i+1]>m+x) m=a[i],ans++;
	}
	if (a[n]>m+x) printf("-1");
	else printf("%d",ans+1);
	return 0; 
}

Problem I 最美子陣列

思路

動態規劃。
給定陣列x[i]。
狀態:開一個數組f[i],記錄前i個數的最美后綴和。
轉移:f[i]=max(f[i-1]+a,a)
初狀態:f[1]=x[1] //個人喜歡陣列從1開始用
答案:max{f[i]}

然而正解是貪心。
用一個sum記錄當前字首和,遍歷整個陣列,如果字首和sum變成了負數,當前數就不需要加上前面的數了(還不如只選它自己),這時把sum置為0,再繼續累加。
這個問題通常被稱為最大子段和問題。

程式碼

#include <stdio.h>
int main()
{
	int n;
	long long sum=-2147483648,ans=-2147483648,a;
	for (scanf("%d",&n);n--;)
	{
		scanf("%lld",&a);
		if (sum<0) sum=a;
		else sum+=a;
		if (ans<sum) ans=sum;
	}
	printf("%lld",ans);
}