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分為兩種情況。
- 此位是0。先算前面0的貢獻,考慮維護一個cnt[2]數組,令每遇到一個1就讓cur異或1,統計0的個數,相當於按1劃分0的段,顯然此位的答案應加上cnt[cur^1]。然後算1的貢獻,應該是此位之前1的中,距它中間間隔了正偶數個1的1的個數。
- 此位是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 古代龍人的謎題