进行一个魔的除题解
sail_with_pleasure · · 题解
为了与题面统一,称
算法零
由于状态只有
时间复杂度
算法一
按算法四的思想模拟即可,不过多赘述。
时间复杂度
算法二
考虑初始灯全为
这个时候,勇士可以直接贪心地先点亮所有编号为奇数的格子,这样魔王每次只能消除一个
可以选择任何计算方法,时间复杂度
算法三
考虑数据随机。
一些缺少一些细节的贪心以及乱搞做法应该都能通过。
算法四
考虑魔王的每次消除
- A 段:若干次连续消除两个
\tt 1 (可能没有); - B 段:若干次消除一个
\tt 1 (可能没有); - C 段:若干次连续消除两个
\tt 1 (如果一开始就是全\tt 1 那也可能没有)。
由于 A 段和 C 段每一天都只比前一天增加一盏亮着的灯,所以只需求出在 B 段花费的时间即可计算总时间。
考虑 A 段:先贪心地模拟魔王,每次消掉两个连续的
在 A 段魔王消除之时,我们可以假设勇士知道魔王会如此消除(事实上,根据魔王每一步的消除,针对性地再点亮一些灯也可行),那么他的最优策略肯定是:在保证任意两个
考虑 B 段:由于魔王每次都会采取最优策略,那么勇士补上每次魔王消掉的那一盏灯,这同样也是最优的。可能有形如
考虑 C 段:在 A / B 段都已经求出来的情况下,可以直接求解。
时间复杂度
代码如下:
#include<bits/stdc++.h>
#define pi pair<int,int>
#define mid (l+r)/2
#define N 1000001
using namespace std;
int t,n,a[N],cnt,cnt1,dp[N],ans,cnt2,flag,flag1,pre[N],nxt[N];
int pan(){//单消段特判
int x=-1;
for(int i=1;i<=n;i++){//计算每两个 1 之间的间隔
if(a[i]){
pre[i]=i-x-1;
nxt[i]=n+1-i;
if(x>0)nxt[x]=pre[i];
x=i;
}
}
for(int i=1;i<=n;i++){//判断是否有较优的单消情况
if(a[i]==1&&((pre[i]%2==1||nxt[i]%2==1)||a[i-1]==1||a[i+1]==1)){
return 0;
}
}
if(a[n]==1)return 0;
return 1;
}
int main(){
cin>>t;
while(t--){
cin>>n;
for(int i=0;i<=n+2;i++){
a[i]=pre[i]=nxt[i]=dp[i]=0;
}
cnt=cnt1=ans=cnt2=flag=flag1=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i])ans++,flag=1;
}
if(pan())flag1=1;
for(int i=1;i<=n;i++){//判断初始双消段的长度
if(a[i]==a[i+1]&&a[i]==1){
a[i]=a[i+1]=0;
cnt++;
}
}
for(int i=1;i<=n;i++){
if(a[i]==1){
dp[i]=max(dp[i-2],dp[i-3]);
}else if(a[i+1]!=1&&a[i-1]!=1){
dp[i]=max(dp[i-2],dp[i-3]);
dp[i]++;
}
cnt1=max(cnt1,dp[i]);
}
if(n<=3){
if(ans<n)cout<<1<<endl;
else cout<<0<<endl;
continue;
}
if(cnt1/3<cnt){//判断是否存在单消段
cout<<n-ans<<endl;
continue;
}
cnt1=cnt1-cnt*3;
if(flag1&&flag)cnt1++;
if(flag)cout<<n-ans-cnt1/2-1<<endl;
else if(n<=5)cout<<2<<endl;
else cout<<n-(cnt1-3)/2-3<<endl;
}
return 0;
}
PS:这题据说可能有神秘的 bitset 或者线段树等做法,大家可以试一试。