hdu6307 Turn Off The Light 2018 Multi-University Training Contest 1 J

Turn Off The Light

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)

 

Problem Description

There are n lights aligned in a row. These lights are numbered 1 to n from left to right. Initially some of the lights are turned on. Chiaki would like to turn off all the lights.
Chiaki starts from the p-th light. Each time she can go left or right (i.e. if Chiaki is at x, then she can go to x−1 or x+1) and then press the switch of the light in that position (i.e. if the light is turned on before, it will be turned off and vise versa).
For each p=1,2,…,n, Chiaki would like to know the minimum steps needed to turn off all the lights.

 

Input

There are multiple test cases. The first line of input is an integer T indicates the number of test cases. For each test case:
The first line contains an integer n (2≤n≤106) -- the number of lights.
The second line contains a binary string s where si=1 means the i-th light is turned on and si=0 means i-th light is turned off.
It is guaranteed that the sum of all n does not exceed 107.

 

Output

For each test cases, output (∑i=1|s|i×zi)mod(109+7), where zi is the number of step needed when Chikai starts at the i-th light.

 

Sample Input

 

3

3

000

3

111

8

01010101

 

Sample Output

 

0

26

432

 

Source

2018 Multi-University Training Contest 1

(刚开始补题的时候顺着比赛的时候的思路写,果然是错的。。最后参考了dls的讲题和代码,终于勉强弄懂了这题。)

题目大意是有n个位置有灯,告诉你每个位置灯是开着的还是关着的,告诉你你的初始位置p,你可以往左或者右移动一步(在1到n的范围里移动),并且在移动后必须按下开关(就是使当前打开的灯关上,当前未打开的灯打开),求出使得所有灯都关上的最少移动步数z[p], 求出p为1-n时所有的答案,输出sum{z[i]*i} %(1e9+7)(1<=i<=n)

首先我们看对于起点为最左边的点,我们可以贪心地往右走,就是若当前的位置为1,那么我们往右走一步再往左走一步走到原来的位置,那么此时的位置上1就变成0了,后一个位置的值取反,若是再往后走,那么后一个位置又会变为自己之前的值;若当前位置为0,就直接往右走一步。直到右边没有1了。

比如111   '1'11 ->1'0'1->'0'01->0'1'1->01'0'->0'0'0  单引号表示当前所在的位置,那么我们可以知道对于111,p=1的情况,z[p]=5

若是我们初始位置p是在中间的话,那我们可以选择先往左走到最左边的点,然后按照上面的方法再做一次,或者先走到最右边的点,重复上面的方法走,取这两种走法的最优值就是z[p]的值了。

那么我们就可以很容易地知道一个O(n^2)的做法了。(就是模拟上面说的走法)

至于怎么优化到O(n) 就是疯狂前缀和了。

我们仍先假设p为最左边的点,我们设最后一个出现的1的位置是last,那么我们需要统计的是(last-p)+2*(走到该点时为1的点的数量),那么重点就是统计在从p走到last的图中有多少个点在被走到时为1。那么我们将所有的s[i]^1以后计算前缀异或和(因为走到这个点这个点必须要改变,所以我们直接算异或1以后的前缀异或和),这就是我们需要统计的值。最后判断一下p-1点的前缀异或就可以知道我们需要累计的答案是哪一部分了。

至于p为中间的点时,我们再算s[i]的前缀异或,这样在先走到一端时,我们可以把走过的部分都取异或,然后和不变的部分拼接起来,按照和之前一样的方法计算就可以了。

具体的实现细节在代码里。(最后感谢dls)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define N 105
#define INF 1e9
#define MOD 1000000007
using namespace std;
int z1[N],z2[N],n,sum[N],sum1[N],num[N],num1[N],T;
char s[N],g[N];
void work(char *s,int *z){
	for(int i=1;i<=n;i++) z[i]=INF;
	int lst=-1,fir=n+1;
	for(int i=1;i<=n;i++)
	if(s[i]==1){
		lst=i;if(fir==n+1) fir=i;
	}
	if(lst==-1) {
		for(int i=1;i<=n;i++) z[i]=0;
		return ;
	}
	for(int i=1;i<=n;i++){
		sum[i]=sum[i-1]^s[i]^1;
		sum1[i]=sum1[i-1]^s[i];
		num[i]=num[i-1]+sum[i];
		num1[i]=num1[i-1]+sum1[i];
	}
	for(int i=1;i<=lst;i++){
		int now=i,ans=0,now1;
		if(i<=fir){			
			if(i==lst) {z[i]=3;continue;}
			ans=lst-now;
			if(sum[now-1]) ans+=2*(num[lst-1]-num[now-1]);
			else ans+=2*(lst-now-num[lst-1]+num[now-1]);
			if(sum[lst-1]^sum[now-1]^1) ans--;
			z[i]=ans;
		}else{			
			ans=lst-fir+now-fir;
			now1=now;now=fir;
			if(sum1[now-1])ans+=2*(num1[now1-1]-num1[now-1]);
			else ans+=2*(now1-now-(num1[now1-1]-num1[now-1]));
			if(sum1[now-1]^sum1[now1-1]^sum[now1-1]) ans+=2*(num[lst-1]-num[now1-1]);
			else ans+=2*(lst-now1-(num[lst-1]-num[now1-1]));
			if(sum[lst-1]^sum[now1-1]^sum1[now1-1]^sum1[fir-1]^1)ans--;
			z[i]=ans;
		}
	}
}
int main(){
	freopen("1.in","r",stdin);
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		scanf("%s",s+1);
		for(int i=1;i<=n;i++) s[i]-='0';
		for(int i=1;i<=n;i++) g[i]=s[n-i+1];
		work(g,z2);
		work(s,z1);		
		ll ans=0;
		for(int i=1;i<=n;i++){
			int x=min(z1[i],z2[n-i+1]);
			ans=(ans+(ll)x*i%MOD)%MOD;
		}
		printf("%lld\n",ans);
	}
}

猜你喜欢

转载自blog.csdn.net/BinGoo0o0o/article/details/81190093