1. 程式人生 > >test20180922 古代龍人的謎題

test20180922 古代龍人的謎題

names 統計 close 約束 ast char s 兩個 枚舉 一位

題意

問題描述

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;
}

test20180922 古代龍人的謎題