2019.10.5 noip模拟赛

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] [xk+1,x+k1],从 x − k + 1 x-k+1 xk+1 x + k − 1 x+k-1 x+k1,每次加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)aisai)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,s1],共 s s s个元素,所以要用 s i s^i si
剩下的 a − i a-i ai行取值可以为 [ 0 , s ] [0,s] [0,s],共 s + 1 s+1 s+1个元素,所以要用到 ( s + 1 ) a − i (s+1)^{a-i} (s+1)ai
因为这 a − i a-i ai行一定要保证每行中至少一个数的值为 s s s,所以当每行的最大值小于 s s s的情况是不合法的,有 s a − i s^{a-i} sai中,所以要减去 s a − i s^{a-i} sai;
求出了 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],二项式反演可以用二项式定理证明。

推广到一般情况,之后我们求出的矩形不一定是完整的矩形,可能会出现以下三种情况,其中红色矩形为上次求出来的矩形:

case1
case2
case3
实际上,第一种情况就已经包含了第二、三种情况。将第一种情况拆分,如下图所示:

case4

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值