Reverse
题目背景
小( ext{G})有一个长度为(n)的(01)串(T),其中只有(T_S=1),其余位置都是(0)。现在小( ext{G})可以进行若干次以下操作:
• 选择一个长度为(K)的连续子串((K)是给定的常数),翻转这个子串。
对于每个(i,iin [1,n]),小( ext{G})想知道最少要进行多少次操作使得(T_i=1).特别的,有(m)个“禁止位置”,你需要保证在操作过程中(1)始终不在任何一个禁止位置上。
输入输出格式
输入格式
从文件reverse.in
中读入数据.
第一行四个整数(n,K,m,S).
接下来一行(m)个整数表示禁止位置。
输出格式
输出到文件reverse.out
中.
输出一行(n)个整数,对于第(i)个整数,如果可以通过若干次操作使得(T_i=1),输出最小操作次数,否则输出(-1).
说明
对于所有数据,有(1≤n≤10^5,1≤S,k≤n,0≤m≤n).
保证(S)不是禁止位置,但禁止位置可能有重复。
-
( ext{Subtask1}(24\%), n≤10).
-
( ext{Subtask2}(22\%), n≤10^3).
-
( ext{Subtask3}(3\%), k=1).
-
( ext{Subtask4}(8\%), k=2).
-
( ext{Subtask5}(43\%)), 没有特殊的约束。
题目其实并不难
发现可以连边直接bfs,可以拿到57pts的暴力分
发现边的数量很多,需要支持动态删点
用两颗平衡树分别维护位置为奇数时和位置为偶数时
然后每次找到可翻转的左边和右边,在平衡上二分,bfs然后删掉点就可以了
每个点只会被删掉一次,复制度差不多是(O(nlogn))的
Code:
#include <cstdio>
#include <cstring>
#include <set>
const int N=1e5+10;
std::set <int> s1,s2;
std::set <int>::iterator it;
int n,k,m,s,l=1,r,q[N<<2],used[N],ans[N],lp,rp,d;
int min(int x,int y){return x<y?x:y;}
int max(int x,int y){return x>y?x:y;}
int main()
{
scanf("%d%d%d%d",&n,&k,&m,&s);
memset(ans,-1,sizeof(ans));
used[s]=1,q[++r]=s,ans[s]=0;
for(int p,i=1;i<=m;i++) scanf("%d",&p),used[p]=1;
for(int i=1;i<=n;i+=2)
{
if(!used[i]) s1.insert(i);
if(!used[i+1]) s2.insert(i+1);
}
while(l<=r)
{
int p=q[l++];
lp=max(p-k+1,max(k-p+1,1)),rp=min(p+k-1,min(2*n+1-k-p,n));
if(p-k&1)//s2
{
it=s2.lower_bound(lp);
while(it!=s2.end()&&(d=*it)<=rp)
{
q[++r]=d;
ans[d]=ans[p]+1;
it++;
s2.erase(d);
}
}
else
{
it=s1.lower_bound(lp);
while(it!=s1.end()&&(d=*it)<=rp)
{
q[++r]=d;
ans[d]=ans[p]+1;
it++;
s1.erase(d);
}
}
}
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
return 0;
}
2018.10.7