1. 程式人生 > 實用技巧 >2019-12-29 Div.3模擬賽題解

2019-12-29 Div.3模擬賽題解

出給學弟的普及模擬賽,現在題解搬到這裡來,估計也沒人看了,墳貼一個。

T1 密碼(password)

題意

給出 \(6\) 個字串,每個字串有一個標號沒有兩個標號相同的字串,標號的範圍為 \(1\sim 6\)。按照標號順序拼成一個新的字串,求這個字串。

對於 \(100\%\) 的資料,\(\sum |S|≤1000\),且保證 \(S\) 中沒有空格字元。

演算法一

基礎題,邊讀入邊開一個string陣列記錄,最後按順序輸出即可。時空複雜度均為 \(O(\sum {|S|})\),期望得分 \(100\) 分。

Code

#include <stdio.h>
#include <iostream>
#include <string.h>
using namespace std;

string s[7];

int main()
{
    freopen("password.in","r",stdin),freopen("password.out","w",stdout); 
	
    int i,x; string c;
    for (i=1; i<=6; i++) cin>>x>>c,s[x]=c;
	
    for (i=1; i<7; i++) cout<<s[i];
	
    return 0;
} 

T2 環繞膜拜(round)

題意

給定平面上不同的 \(n\) 個點,\(n\) 為偶數。求一個點,使得這 \(n\) 個點組成的圖形關於這個點對稱;若不存在這樣的點,輸出 \(-1\)

對於 \(5\%\) 的資料,\(n=2\)
對於 \(50\%\) 的資料,\(1≤n≤1000\)
對於 \(100\%\) 的資料,\(1≤n≤10^6\) , \(-10^6≤|x_i|,|y_i|≤10^6\),並且保證 \(n\) 是一個偶數。
保證若存在對稱點,其為整點。

演算法一

直接輸出兩個點連線,期望得分 \(5\) 分。

我們用 \(cnt_x\) 來表示數值為 \(x\) 的牌出現了多少次。那麼我們就從 \(1\)

\(n\) 列舉對子,再列舉刻子和順子。那麼深搜的時間複雜度是 \(O(2n^2)\),合起來就是 O\((2n^3)\),空間複雜度為 \(O(n)\),期望得分 \(70\) 分。

演算法二

開兩個指標 \(i,j\) 暴力判斷所有的 \(O(n^2)\) 的方式是否滿足。時間複雜度為 \(O(n^2)\),空間複雜度為 \(O(n)\),期望得分 \(50\) 分。

演算法三

我們以 \(x\) 為第一關鍵字,以 \(y\) 為第二關鍵字從小到大排序(水平序)。那麼可能的配對方案只有 \(1\)\(n\) 配對,\(2\)\(n-1\) 配對,\(\ldots\)\(\frac{n}{2}\)

\(\frac{n}{2}+1\) 配對。我們1 \(O(n)\) 判斷一下這個方案是否滿足要求即可。

時間複雜度為 \(O(n\log{n})\)(瓶頸在排序上),空間複雜度為 \(O(n)\),期望得分 \(100\) 分。

Code

#include <stdio.h>
#include <algorithm>
#define il inline
using namespace std;
const int N=1000005;

int n,X,Y;
struct node{int x,y;}d[N];

il bool cmp(node a,node b){return a.x==b.x?a.y<b.y:a.x<b.x;}

int main()
{
    freopen("round.in","r",stdin),freopen("round.out","w",stdout); 
	
    scanf("%d",&n); int i;
    for (i=1; i<=n; i++) scanf("%d%d",&d[i].x,&d[i].y);
    sort(d+1,d+n+1,cmp);

    X=(d[1].x+d[n].x)/2,Y=(d[1].y+d[n].y)/2;
    for (i=2; i<=(n+1)/2; i++) if ((d[i].x+d[n-i+1].x)/2!=X||(d[i].y+d[n-i+1].y)/2!=Y) return 0*puts("There is not a place like that.");
    printf("%d %d",X,Y);

    return 0;
}

T3 花生蛋糕(cake)

題意

一個人帶著若干個物品奔跑,速度為每秒一個單位長度。有 \(n\) 個站點,第 \(i\) 個站點在位置 \(s_i\) 處,這個人要將 \(h_i\) 個物品放到此處,放下後這個人就不在帶著這些物品奔跑了。

在時刻 \(t\) 把物品放到 \(i\) 站點處會增加 \(f_i\times t\) 的疲勞值,帶著 \(k\) 個物品奔跑一個單位長度會增加 \(k\) 的疲勞值。

對於 \(5\%\) 的資料,\(k=1\)
對於 \(15\%\) 的資料,\(n,m≤3,k≤7\)
對於另外 \(5\%\) 的資料,\(k=n×m\)
對於 \(100\%\) 的資料,\(1≤n,m≤300,1≤k≤n×m\)

演算法一

直接全部輸出1即可,期望得分 \(5\) 分。

演算法二

\(1\)\(n\times m\) 各輸出一次即可,期望得分 \(5\) 分,期望總得分 \(10\) 分。

演算法三

暴力搜尋怎麼切割,時間複雜度為 \(O(nm^k)\),空間複雜度為 \(O(nm)\),期望得分 \(15\) 分期望總得分 \(20\) 分。

演算法四

這裡只提供一種構造方案。

從上到下走,先走到第一個關鍵點個數非 \(0\) 行,設此時到了第 \(z+1\) 行。

接著對剩下的 \(n-z\) 行,分兩種情況討論:

  • 這行的關鍵點數目非 \(0\),設其有 \(x\) 個花生。
    那麼對於前 \(x-1\) 個,染色染到其位置為止;對於最後一個,則把剩下的格子都染上。
  • 這行的關鍵點數目為 \(0\)
    那麼直接和上一行染成一樣的就行了。

最後把第1到 \(z\) 行,直接把其染成和第 \(z+1\) 的顏色就行了。

時空複雜度均為 \(O(nm)\),期望得分 \(100\) 分。

Code

#include <stdio.h>
using namespace std;
const int N=305;

int n,m,k,last,z,ans[N][N]; char mp[N][N];

int main()
{
    freopen("cake.in","r",stdin),freopen("cake.out","w",stdout);
	
    scanf("%d%d%d",&n,&m,&k); int i,j,cnt;
    for (i=1; i<=n; i++)
    {
	scanf("%s",mp[i]+1);
	for (j=1,cnt=0; j<=m; j++) cnt+=mp[i][j]=='#';
	if (!cnt) 
	{
	    if (z==i-1) z++;
            else{for (j=1; j<=m; j++) ans[i][j]=ans[i-1][j];}
	}
	else
	{
	    for (j=1,last++; j<=m; j++)
	    {
		ans[i][j]=last;
		if (mp[i][j]=='#') if (--cnt!=0) last++;
	    }
	}
    }
    for (i=z; i>=1; i--) for (j=1; j<=m; j++) ans[i][j]=ans[i+1][j];
    for (i=1; i<=n; i++) for (j=1; j<=m; j++) printf("%d%c",ans[i][j]," \n"[j==m]);
	
    return 0;
}

T4 零線火線(powerline)

題意

一個人的初始生命值為 \(HP\) (生命值無上限),接下來 \(n\) 秒,他每秒會受到一次傷害,第 \(i\) 秒的傷害值為 \(a_i\) 。任何時刻,若 \(HP≤0\) ,則視為死亡。

這個人有回血技能:
每受到一次傷害,就會積累一點能量。每次使用能力,就會使用所積累的所有能量,恢復 \(15\times\) 能量點數的生命值,並且相鄰兩次使用的時間至少要有 \(CD\) 秒的間隔。

求不會死亡的 \(CD\) 的最小值。

對於 \(30\%\) 的資料,\(n≤12\)
對於 \(100\%\) 的資料,\(1≤n≤500,0≤a_i≤2000\)

演算法一

直接暴力搜尋每個位置填上什麼數,時間複雜度為 \(O(n^m)\),空間複雜度為 \(O(n)\),期望得分 \(50\) 分。

演算法二

首先答案是滿足單調性的,可以二分答案,轉化為判定性問題。

接下來我們設 \(d_i\) 表示第 \(1\)\(i\) 秒釋放能力的次數,記 \(sum_i=\sum\limits_{j=1}^{i}{a_j}\)

那麼對於 \(d_i\),我們有如下幾種限制:

  • 因為一秒內最多隻能釋放一次技能,所以有 \(0\le d_i-d_{i-1}\le 1\)
  • 假設二分值為 \(CD\),那麼因為兩次釋放能力的間隔少有 \(CD\) 秒,所以有 \(d_i-d_{i-CD}\le 1\)
  • 設為了存活,到第 \(i\) 秒至少需要釋放 \(pos\) 的能量,則 \(pos\) 必須滿足:\(15pos+HP-sum_{i+1}\le 1\)
    又因為 \(pos\) 要最小,所以 \(pos=\lceil \frac{sum_{i+1}-HP+1}{15} \rceil\)
    所以在第 \(pos\)\(i\) 秒必須釋放一次能力,所以有 \(d_i-d_{pos-1}\ge 1\)

發現這些都是差分約束類的,於是直接建圖看最短(長)路是否存在,即是否有負(正)環即可,這可用 DFS 版 SPFA 實現。

時間複雜度最壞是 \(O(nm\log)\) 的,實際遠遠達不到這個上界,空間複雜度為 \(O(n)\),期望得分 \(100\) 分。

#include <cstdio>
#include <cmath>
#define il inline
using namespace std;
const int N=505,M=1e5+5;

int n,hp,cd=-1,a[N],sum[N],dis[N],ok[N];
int to[M],nx[M],wt[M],hd[N],sze;

il void add(int u,int v,int w){to[++sze]=v,nx[sze]=hd[u],wt[sze]=w,hd[u]=sze;}

il int SPFA(int u)
{
    ok[u]=1; int i,v;
    for (i=hd[u]; i!=n+1; i=nx[i])
	if (dis[v=to[i]]<dis[u]+wt[i])
	{
	    dis[v]=dis[u]+wt[i];
	    if (ok[v]||SPFA(v)) return 1;
	}
    return ok[u]=0;
}

il bool check(int x)
{
    int i,o=0; sze=0;
    for (i=0; i<=n; i++) dis[i]=ok[i]=0,hd[i]=n+1;
    for (i=1; i<=n; i++) add(i-1,i,0),add(i,i-1,-1);
    for (i=x; i<=n; i++) add(i,i-x,-1);
    for (i=1; i<n; i++)
    {
	if (sum[i+1]-hp+1<=0) continue;
	int pos=ceil(1.0*(sum[i+1]-hp+1)/15.0);
	if (pos>i) return 0;
	add(pos-1,i,1);
    }
    for (i=0; i<=n&&!o; i++) o|=SPFA(i);
    return !o;
}

int main()
{
    freopen("powerline.in","r",stdin),freopen("powerline.out","w",stdout); 
	
    scanf("%d%d",&n,&hp); int i;
    for (i=1; i<=n; i++) scanf("%d",a+i),sum[i]=sum[i-1]+a[i];
	
    int l=1,r=n,mid;
    while (l<=r)
    {
	mid=l+r>>1;
	if (check(mid)) cd=mid,l=mid+1;
	else r=mid-1;
    }
	
    if (cd==n) printf("Peanut can play with the wires at will.");
    else printf("%d",cd);
	
    return 0;
}