T1 Reverse
【题目描述】
小G有一个长度为n的01串T,其中只有
T
S
T_S
TS = 1,其余位置都是0。
现在小G可以进行若干 次以下操作:
• 选择一个长度为K的连续子串(K是给定的常数),翻转这个子串。
对于每个 i , i ∈ [ 1 , n ] i, i ∈ [1, n] i,i∈[1,n],小G想知道 少要进行多少次操作使得 T i = 1 T_i = 1 Ti=1.
特别的,有m个“禁 止位置”,你 要保证在操作过程中1始终不在任何一个禁止位置上。
【输入格式】
第1行四个整数n, K, m, S. 接下来1行m个整数表示禁止位置。
【输出格式】
输出1行n个整数,对于第
i
i
i个整数,如果可以通过若干次操作使得
T
i
=
1
T_i = 1
Ti=1,输出 小操作 次数,否则输出-1.
解析
手推一遍变换过程,例如当k=4时有以下变换:
原数列: 0 0 0 0 0 0 0 1 0 0 0 0 0 0
变换1: 0 0 0 0 1 0 0 0 0 0 0 0 0 0
变换2: 0 0 0 0 0 0 1 0 0 0 0 0 0 0
变换3: 0 0 0 0 0 0 0 0 1 0 0 0 0 0
变换4: 0 0 0 0 0 0 0 0 0 0 1 0 0 0
可以发现,当当前位置为 x x x时,可变换的位置为区间 [ x − k + 1 , x + k − 1 ] [x-k+1,x+k-1] [x−k+1,x+k−1],从 x − k + 1 x-k+1 x−k+1到 x + k − 1 x+k-1 x+k−1,每次加2,说明变换位置奇偶性相同。
想到了什么?bfs啊!
先将 S S S位置的节点入队,对其进行广搜,将其遍历的节点入队。注意不要让禁止位置入队! 为了防止一个节点多次入队,还要对该节点打标记。
可是,你会惊喜地发现,你 T L E TLE TLE了!理论上每个节点只访问1次,但还是会经过,所以复杂度是 O ( n k ) O(nk) O(nk)的。
考虑优化,我们只能让每个点经过一次,第二次遇到了就直接跳过。可以用 s e t set set优化。 s e t set set是一种特殊的容器,每个节点只会被记录1次,保证不会重复。所以我们可以分别对奇数和偶数的点开两个 s e t set set,处理时分别处理。注意,节点用一次就删除哦!
还有一个方法是线段树优化建图,可是我太懒了,会了懒得写。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#define maxn 1000005
using namespace std;
int n,k,m,s,cnt;
int lim[maxn],used[maxn];
int que[maxn],ans[maxn];
set<int>s1,s2;
void bfs()
{
int head=0,tail=0,st,ed;
que[++tail]=s;
used[s]=1;
while(head<tail)
{
int x=que[++head];
if(cnt==n)break;
st=max(1,x-k+1);
ed=min(n,x+k-1);
st=st+(st+k-1)-x;
ed=ed+(ed-k+1)-x;
if(st&1)
{
for(set<int>::iterator it=s1.lower_bound(st);it!=s1.end()&&*it<=ed;s1.erase(it++))
{
que[++tail]=*it;
ans[*it]=ans[x]+1;
}
}
else
{
for(set<int>::iterator it=s2.lower_bound(st);it!=s2.end()&&*it<=ed;s2.erase(it++))
{
que[++tail]=*it;
ans[*it]=ans[x]+1;
}
}
}
}
int main()
{
freopen("reverse.in","r",stdin);
freopen("reverse.out","w",stdout);
memset(ans,-1,sizeof(ans));
scanf("%d%d%d%d",&n,&k,&m,&s);
ans[s]=0;
for(int i=1;i<=m;++i)
{
int x;
scanf("%d",&x);
if(!lim[x])cnt++;
lim[x]=1;
}
for(int i=1;i<=n;i++)
{
if(lim[i]||i==s)continue;
if(i&1)
s1.insert(i);
else s2.insert(i);
}//插入
bfs();
for(int i=1;i<=n;++i)
printf("%d ",ans[i]);
return 0;
}
总结
S T L STL STL真的很好用!
T2 Silhouette
【题目描述】
有一个
n
×
n
n × n
n×n的网格,在每个格子上堆叠了一些边长为1的立方体。
现在给出这个三维几何体的正视图和左视图,求有多少种与之符合的堆叠立方体的方案。两种方案被认为是不同的,当且仅当某个格子上立方体的数量不同.
输出答案对 1 0 9 + 7 10^9 + 7 109+7取模的结果。
【输入格式】
第1行一个整数n.
第二行n个整数,第i个表示正视图中从左到右第i个位置的高度 A i A_i Ai。
第三行n个整数,第i个表示左视图中从左到右第i个位置的高度 B i B_i Bi。
【输出格式】
输出1行表示答案。
解析
题目即求 m a x i = 1 n X i , j = B i , m a x j = 1 n X i , j = A i max_{i=1}^nX_{i,j}=B_i,max_{j=1}^nX_{i,j}=A_i maxi=1nXi,j=Bi,maxj=1nXi,j=Ai的解的个数。
对于点 ( i , j ) (i,j) (i,j),该点的最大值为 m i n ( A i , B j ) min(A_i,B_j) min(Ai,Bj)。
可以证明,答案与 A , B A,B A,B的顺序无关。所以我们先将 A , B A,B A,B从大到小排个序,新开一个数组 S S S记录 A A A和 B B B出现的所有值,注意要去重。之后我们从大到小枚举 S S S中的值,在 A A A, B B B中寻找出现的该值得个数,这样做的目的是使枚举到的点能取值的最大值相等。最后这些最大值相等的点汇成了一个矩形,以第一次为例:
设矩形有
a
a
a行
b
b
b列,
d
p
[
i
]
dp[i]
dp[i]表示至少
i
i
i行不合法的方案数,即这
i
i
i行中的最大值一定小于
s
s
s,节点的最大值为
s
s
s,这里先给出状态转移公式:
d
p
[
i
]
=
C
(
a
,
i
)
×
(
s
i
×
(
s
+
1
)
a
−
i
−
s
a
−
i
)
b
dp[i]=C(a,i)\times(s^i\times(s+1)^{a-i}-s^{a-i})^b
dp[i]=C(a,i)×(si×(s+1)a−i−sa−i)b
其中
C
(
a
,
i
)
C(a,i)
C(a,i)表示组合数,因为在
a
a
a行中选取
i
i
i行,所以要用到组合数;
选取的
i
i
i行中的取值范围为
[
0
,
s
−
1
]
[0,s-1]
[0,s−1],共
s
s
s个元素,所以要用
s
i
s^i
si;
剩下的
a
−
i
a-i
a−i行取值可以为
[
0
,
s
]
[0,s]
[0,s],共
s
+
1
s+1
s+1个元素,所以要用到
(
s
+
1
)
a
−
i
(s+1)^{a-i}
(s+1)a−i;
因为这
a
−
i
a-i
a−i行一定要保证每行中至少一个数的值为
s
s
s,所以当每行的最大值小于
s
s
s的情况是不合法的,有
s
a
−
i
s^{a-i}
sa−i中,所以要减去
s
a
−
i
s^{a-i}
sa−i;
求出了
d
p
[
0
]
dp[0]
dp[0]到
d
p
[
n
]
dp[n]
dp[n],我们的目标是0行不合法的方案数,根据容斥定理以及二项式反演(我也不知道是什么东西),该正方形对答案的贡献为
d
p
[
0
]
−
d
p
[
1
]
+
d
p
[
2
]
−
⋯
dp[0]-dp[1]+dp[2]-\cdots
dp[0]−dp[1]+dp[2]−⋯,二项式反演可以用二项式定理证明。
推广到一般情况,之后我们求出的矩形不一定是完整的矩形,可能会出现以下三种情况,其中红色矩形为上次求出来的矩形:
实际上,第一种情况就已经包含了第二、三种情况。将第一种情况拆分,如下图所示: