test20180922 古代龙人的谜题

题意

问题描述

Mark Douglas是一名调查员。他接受了「调查古代龙人」的任务。经过千辛万苦,Mark终于找到了一位古代龙人。Mark找到他时,他正在摆弄一些秘药,其中一些药丸由于是从很久以前流传下来的,发出了独特的光泽。古代龙人告诉了Mark一些他想知道的事情,看了看手中的秘药,决定考一考这位来访者。
古代龙人手中共有n粒秘药,我们可以用1表示「古老的秘药」,其余的用0表示。他将它们排成一列。古代龙人认为平衡是美的,于是他问Mark能选出多少个「平衡的区间」。「平衡的区间」是指首先选出一个区间[L, R],在它内部选出一个中间点mid,满足L<mid<R,mid是「古老的秘药」,且区间[L, mid]和[mid, R]中「古老的秘药」个数相等。

输入格式

输入文件名为puzzle.in。
第一行为一个正整数idx表示该测试点所属的子任务编号,子任务的详细信息请见「数据范围」。样例的子任务编号为0。
第二行为一个正整数n。
第三行为一个长度为n的字符串,仅包含0和1。

输出格式

输出文件名为puzzle.out。
输出仅一行表示答案。

样例输入

0
7
1101011

样例输出

7

数据范围

本题采用捆绑测试,只有通过一个子任务中的全部测试点才能拿到这个子任务的分数。
对于所有子任务:1≤n≤〖10〗^6。各子任务分值及特殊约束如下:
子任务1(8%):1≤n≤50。
子任务2(26%):1≤n≤300。
子任务3(24%):1≤n≤2000。
子任务4(5%):n≥3,字符串中仅有3个1。
子任务5(12%):字符串中全是1。
子任务6(25%)。

分析

考场做法

考虑分别计算以i为结尾,起始在[1,i-1]内的合法区间个数。
按串的0/1分为两种情况。

  1. 此位是0。先算前面0的贡献,考虑维护一个cnt[2]数组,令每遇到一个1就让cur异或1,统计0的个数,相当于按1划分0的段,显然此位的答案应加上cnt[cur^1]。然后算1的贡献,应该是此位之前1的中,距它中间间隔了正偶数个1的1的个数。
  2. 此位是1。先算前面0的贡献,应该是与此1同段的,且是非相邻0段的0的个数。所以要维护一个last数组,代表此位之前最近的1的位置。然后算1的贡献,应该是此位之前的1中,距它中间间隔了正奇数个1的1的个数。

代码

#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x)
{
    T data=0;
	int w=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
    while(isdigit(ch))
        data=10*data+ch-'0',ch=getchar();
    return x=data*w;
}
typedef long long ll;
const int INF=0x7fffffff;

const int MAXN=1e6+7;

int n;
char s[MAXN];

int last[MAXN];
int pre[MAXN];
int cnt[2],cur=0;
int f[MAXN];
/*
0
5
10010
*/
int main()
{
  freopen("puzzle.in","r",stdin);
  freopen("puzzle.out","w",stdout);
	int id;
	read(id);
	read(n);
	scanf("%s",s+1);
	ll ans=0;
	for(int i=1;i<=n;++i)
	{
		pre[i]=pre[i-1]+(s[i]=='1');
		if(s[i]=='0')
		{
			f[i]=cnt[cur^1]+(pre[i]-1)/2;
			++cnt[cur];
			last[i+1]=last[i];
		}
		else if(s[i]=='1')
		{
			f[i]=pre[i-1]/2+cnt[cur]-(i-last[i]-1);
//			cerr<<" cnt="<<cnt[cur]<<" judge="<<judge(i)<<endl;
			cur^=1;
			last[i+1]=i;
		}
//		cerr<<i<<" f="<<f[i]<<endl;
//		cerr<<" cur="<<cur<<" cnt="<<cnt[cur]<<endl;
		ans+=f[i];
	}
	printf("%lld\n",ans);
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}

题解

由于当我们确定了区间的两个端点以后,合法的中间点最多只有一个,且只要中间点存在(即区间[L, R]内有奇数个1)该区间就合法,所以我们枚举左端点L,同时维护在L右边且区间[1, R]内1的个数为奇数/偶数的点R的个数,答案就可以统计出来了。

std

#include <cstdio>

using LL = long long;

const int MAXN = 1e6 + 5;

int n, seq[MAXN];
int cnt, right[MAXN];
LL sum[2];

int main() {
#ifndef LOCAL
    freopen("puzzle.in", "r", stdin);
    freopen("puzzle.out", "w", stdout);
#endif
    
    scanf("%*d%d", &n); cnt = 0;
    for (int i = 1; i <= n; ++i) {
        scanf("%1d", seq + i);
        cnt += seq[i];
    }
    
    int tmp = 0;
    for (int i = n, j = cnt; i; --i) {
        ++tmp;
        if (seq[i] == 1) {
            right[j--] = tmp; tmp = 0;
        }
    }
    sum[0] = sum[1] = 0;
    for (int i = 1; i <= cnt; i += 2) sum[1] += right[i];
    for (int i = 2; i <= cnt; i += 2) sum[0] += right[i];
    
    LL ans = 0;
    
    tmp = 0;
    for (int i = 1, j = 1; i <= n; ++i) {
        ++tmp;
        if (seq[i] == 1) {
            ans += (tmp - 1ll) * (right[j] - 1ll) + tmp * (sum[j & 1] - right[j]);
            sum[j & 1] -= right[j];
            ++j; tmp = 0;
        }
    }
    
    printf("%lld\n", ans);
    
    return 0;
}

posted on 2018-09-22 21:37  autoint  阅读(736)  评论(0编辑  收藏  举报

导航