題面
[USACO12NOV]同時平衡線Concurrently Balanced Strings
題解
考慮DP。
\(f[i]\)表示以\(i\)爲左端點的合法區間個數。令\(pos[i]\)表示以\(i\)爲左端點,最靠左的合法右端點。
那麼有如下轉移:
\(f[i] = f[pos[i] + 1] + 1\).
1表示[i, pos[i]]這段合法區間,\(f[pos[i] + 1]\)表示在這段合法區間的基礎上,還可以在後面拼接更多合法區間。
那麼我們的目的就是求\(pos[i]\).
考慮一段區間爲什麼合法?
我們令左括號爲1,右括號爲-1,然後對於每一行單獨求前綴和。
那麼對於每行單獨考慮,一個區間\([l, r]\)需要滿足2個限制:
- \(sum[r] - sum[l - 1] = 0\)
- 區間內任意一點\(i\)滿足\(sum[i] - sum[l - 1] >= 0\)
考慮分開處理這2個限制。
對於每個左端點\(i\),處理出使得區間前綴爲負的第一個點,那麼只要右端點不超過這個點,就滿足第二點限制。
對於同一列的每一行都求出這個值,然後取min,得到值\(t\),表示第\(i\)列的右端點只要不超過\(t\)就可以對於\(k\)行都滿足第二點限制。
令這個最小的,令左端點\(i\)不滿足第二點限制的右端點爲\(lim[i]\)
然後從大到小枚舉列\(i\),那麼我們要處理的就是第一點限制。
只需要查詢最近的一個右端點滿足當前列的\(k\)行與第\(i - 1\)列的\(k\)行相同即可,
如果這個最近的右端點都大於等於\(lim[i]\),那麼對於這個左端點就沒有合法方案了。
否則我們就找到了\(pos[i]\).
那麼這兩個東西應該如何求呢?
求\(lim[i]\):
先對每一行求出對應的右端點,然後取\(min\)即可,接下來講如何對於每行求這個右端點。
如果有右端點滿足使得以\(i\)爲左端點的區間前綴和爲負,那麼肯定會有一個右端點最先滿足這個東西,也就是\(sum[j] - sum[i - 1] = -1\).那麼我們查詢滿足\(sum[j] = sum[i - 1] - 1\)的最小值即可。
這個我們從大到小枚舉\(i\),用\(minn[i]\)表示到當前爲止,值爲\(i\)的點的最小下標是多少,然後就可以直接得到了。
求\(pos[i]\):
用和上面類似的方法,只不過我們這次是要找一個使得\(k\)元組相等的最小\(j\)。
因爲\(k\)很小,所以可以把\(k\)個值都存到\(map\)裏暴力找。
當然也有更優美一點的做法,對這\(k\)個數進行\(hash\)然後再丟\(map\)裏面找,這樣\(map\)就只用存1個數了。
#include<bits/stdc++.h>
using namespace std;
#define R register int
#define LL long long
#define AC 12
#define ac 100100
#define inf 2139062143
#define base 13
#define us signed
#define bu 16777215
#define h(x) (x & bu)
#define g(x) (x + 50000)
int n, m;
LL ans, f[ac];
int lim[ac], minn[ac];
int Head[AC], date[ac], Next[ac], tot;
char s[ac];
map <us LL, int> M;
struct node{
int s[AC]; us LL opt;
friend bool operator == (node a, node b)
{
for(R i = 1; i <= m; i ++) if(a.s[i] != b.s[i]) return false;
return true;
}
}sum[ac];
inline void upmax(int &a, int b) {if(b > a) a = b;}
inline void upmin(int &a, int b) {if(b < a) a = b;}
inline int read()
{
int x = 0;char c = getchar();
while(c > '9' || c < '0') c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x;
}
/*void ins(int w)//插入sum[w]
{
int f = h(sum[w].opt);bool done = false;
for(R i = Head[f]; i; i = Next[i])
{
int now = date[i];
if(sum[now] == sum[w]) upmin(date[i], w);//保留更小的下標
}
if(!done) Head[++ tot] = w, Next[tot] = Head[f], Head[f] = tot;
}
int find(int w)//找到值與sum[w]相同的,最小的x
{
int go = h(sum[w].opt);
for(R i = Head[go]; i; i = Next[i])
{
int now = date[i];
if(sum[now] == sum[w]) return now;
}
return inf;
}*/
void pre()
{
m = read(), n = read();
for(R i = 1; i <= m; i ++)
{
scanf("%s", s + 1);
for(R j = 1; j <= n; j ++)
sum[j].s[i] = sum[j - 1].s[i] + ((s[j] == ')') ? -1 : 1);
}
}
void build()
{
for(R i = 1; i <= n; i ++) lim[i] = inf;
for(R i = 1; i <= m; i ++)
{
memset(minn, 127, sizeof(minn));
for(R j = n; j; j --)//對於每行的每個點找右邊的,最近的,區間和爲-1的點(比當前前綴小1
{
upmin(minn[g(sum[j].s[i])], j);//先加入,防止第一個括號是右括號
upmin(lim[j], minn[g(sum[j - 1].s[i] - 1)]);//判斷[l, r] = 0,需要用到sum[l - 1]和sum[r]
}
}
for(R i = 1; i <= n; i ++)
for(R j = 1; j <= m; j ++)
sum[i].opt = sum[i].opt * base + sum[i].s[j];
}
void work()
{
for(R i = n; i; i --)
{
int pos = M[sum[i - 1].opt];//找最小的和當前l - 1前綴相同的前綴
if(pos && pos < lim[i]) f[i] = f[pos + 1] + 1, ans += f[i];
if(M[sum[i].opt]) upmin(M[sum[i].opt], i);
else M[sum[i].opt] = i;
}
printf("%lld\n", ans);
}
int main()
{
// freopen("in.in", "r", stdin);
pre();
build();
work();
// fclose(stdin);
return 0;
}