Preface
這次狀態不是很好,B 題做的稍微久了一點。D 題一眼秒出做法,可惜很多細節沒考慮到導致 WA 了三發。
下次應當在做題的時候多多考慮細節,手推資料。而不要想著撞大運撞對。
A. Moving Chips
每一次移動可以補齊一個 \(0\),所以找 \(1\) 中穿插的 \(0\) 的數量即可,注意兩邊的 \(0\) 要忽略。
點選檢視程式碼
#include<cstdio>
using namespace std;
const int N=55;
int n;
bool a[N];
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int left=1,right=n;
while(!a[left]) left++;
while(!a[right]) right--;
if(left<=right)
{
int ans=0;
for(int i=left;i<=right;i++)
if(!a[i]) ans++;
printf("%d\n",ans);
}
else printf("0\n"); //all 0
}
return 0;
}
B. Monsters Attack!
這道題做的稍久一點。
因為向左和向右打沒有任何區別,所以左右兩邊的怪物可以全部對映到一邊(把左邊翻轉到右邊)。
然後運用貪心,每次打距離最近的怪物。設 \(sum_i\) 表示在區間 \([1,i]\) 中怪物的血量之和,也是需要的子彈數量,\(i\) 同時也是這個區間內所有怪物走到 \(0\) 所用時間,\(i \times k\) 就是這段時間內可以打出去的子彈數量。
如果存在 \(i\) 使得 \(i\times k < sum_i\),代表在時間 \(i\) 以內,子彈數量不足以在有怪物到達 \(0\) 之前清空所有怪物,可憐的玩家就被吃掉啦。
否則,如果所有的 \(i \times k \ge sum_i\),那麼就可以保證怪物一定會在到達之前被處理掉。
點選檢視程式碼
#include<cstdio>
using namespace std;
const int N=3e5+5;
int n,k,hp[N],pos[N];
long long sum[N];
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&hp[i]);
for(int i=1;i<=n;i++)
{
scanf("%d",&pos[i]);
if(pos[i]<0) pos[i]=-pos[i];
sum[pos[i]]+=hp[i];
}
bool ans=true;
for(int i=1;i<=n;i++)
{
sum[i]+=sum[i-1];
if(sum[i]>1ll*i*k)
{
ans=false;
break;
}
}
printf("%s\n",ans?"YES":"NO");
for(int i=1;i<=n;i++)
sum[i]=0;
}
}
C. Find B
假設查詢區間內沒有 \(1\),那麼 \(b\) 可以很容易構造出來,比如說 \(b\) 可以由一個特別大的數和一大堆 \(1\) 構成。
但是有 \(1\) 就不一樣了。貪心地想,為了儘量降低這些 \(1\) 的影響,應該在構造的 \(b\) 中對這些數取 \(2\),因為總和一定,所以這樣可以讓剩下的數之和儘量大,剩下的數的發揮空間也就更大。
而剩下的數想要能夠將總和補齊,最多也就是全部在 \(b\) 中降成 \(1\),如果剩下不是 \(1\) 的數全部降成 \(1\) 後仍然不能補齊總和,代表這個區間無法構造合法的 \(b\)。
具體來說,設區間長度為 \(len\),區間總和為 \(sum\),\(1\) 的數量為 \(one\),那麼不是 \(1\) 的數的數量就是 \(len-one\),這些數的總和是 \(sum-one\),在 \(b\) 中,它們的和最多可以降低 \((sum-one)-(len-one)=sum-len\),而將全部的 \(1\) 升為 \(2\) 至少需要增加 \(one\),那麼如果 \(sum-len \ge one\),輸出 YES
,否則輸出 NO
。
點選檢視程式碼
#include<cstdio>
using namespace std;
const int N=3e5+5;
int n,q,a[N];
int one[N]; long long sum[N];
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]==1) one[i]=one[i-1]+1;
else one[i]=one[i-1];
sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=q;i++)
{
int l,r; scanf("%d%d",&l,&r);
if(l==r) printf("NO\n");
else
{
int len=r-l+1,tone=one[r]-one[l-1];
long long tsum=sum[r]-sum[l-1];
if(sum-len>=tone) printf("YES\n");
else printf("NO\n");
}
}
}
return 0;
}
D. Slimes
真噁心,洛谷怎麼敢評的黃?
首先,如果某段區間內所有史萊姆不是全部一樣,那麼最後一定可以合成為一個大史萊姆。
其次,因為史萊姆只能吃相鄰的史萊姆,所以某個史萊姆一定是被左右兩邊的史萊姆吃掉的,這裡我們先討論被左邊的史萊姆吃掉,被右邊吃掉只需要翻轉一下序列再求一下就可以了,甚至不用多寫一個函式。
設需要吃掉史萊姆 \(i\) 所需要的最短區間為 \([l,i-1]\),\(l\) 就需要在保證 \(\sum_{j=l}^{i-1}a_j > a_i\) 的前提下儘量大。設 \(sum\) 為 \(a\) 的字首和,上式可以轉化成 \(sum_{i-1}-sum_{l-1}>a_i\),進一步轉化為 \(sum_{i-1}-a_i>sum_{l-1}\)。當列舉 \(i\) 的時候,因為 \(i\) 確定,所以 \(sum_{i-1}-a_i\) 確定,又因為 \(sum_{l-1}\) 具有單調性,所以可以二分來找到滿足上述條件的最大 \(l\)。
二分可以直接用 lower_bound
,然而因為合併的區間元素不能全部相同,所以還要記錄“最後一個與 \(a_{i-1}\) 不同的數”的位置 \(end\),在這個位置以後直到 \(i-1\) 的所有數都相同,所以找的時候只能在這個位置及以前找。
同時還要特判 \(a_i\) 單被 \(a_{i-1}\) 吃的情況,此時雖然不能合併,但仍然能吃。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=3e6+5;
int n,ans[N];
long long a[N],sum[N];
void Solve(bool is_rev)
{
int end=0;
for(int i=1;i<=n;i++)
{
sum[i]=sum[i-1]+a[i];
int cur=is_rev?n-i+1:i;
if(a[i-1]>a[i]) ans[cur]=1;
if(a[i]<sum[i-1] && end) //end>0保證區間合法
{
if(a[i]<a[i-1]) ans[cur]=min(ans[cur],1);
int pos=lower_bound(sum+1,sum+(end-1)+1,sum[i-1]-a[i])-sum-1+1;
ans[cur]=min(ans[cur],i-pos);
}
if(a[i]!=a[i-1]) end=i-1;
}
return;
}
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
ans[i]=0x3f3f3f3f;
}
Solve(false);
reverse(a+1,a+n+1);
Solve(true);
for(int i=1;i<=n;i++)
printf("%d ",ans[i]==0x3f3f3f3f?-1:ans[i]);
putchar('\n');
}
return 0;
}
``