按行枚举(即枚举下边界)
e . g . e.g. e.g.
00000 00000 00000
00110 00110 00110
00100 00100 00100
01111 01111 01111
11110 11110 11110
以最后一行为例,首先找出每个元素向上连续1的个数: 12420 12420 12420
然后我们可以知道,区间 [ 1 , 4 ] [1,4] [1,4] [ 2 , 3 ] [2,3] [2,3] [ 3 , 3 ] [3,3] [3,3]是三个可能的答案(左边、右边、上边都已经无法拓展)所以只要判断某个可能的区间的下一行有没有0就可以了。有0说明无法拓展,即合法答案;否则说明还能够继续拓展,答案在之后的行会累积。
如何找到合法区间:可以用单调栈找出每个数右边(左边)第一个比它的元素,就能够找到合法(最长)区间,注意区间可能会重复
如何判断下一行有没有0:查询区间和是否等于区间长度即可
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define N 3005
using namespace std;
int n,m,i,j,res,l,r,len;
char ch[N];
int a[N][N],sum[N],f[N],lfar[N],rfar[N],b[N][N];
int main()
{
scanf("%d%d",&n,&m);
fo(i,1,n)
{
scanf("%s",ch+1);
fo(j,1,m) a[i][j] = ch[j] - '0';
}
fo(i,1,n)
{
fo(j,1,m) sum[j] = sum[j-1] + a[i+1][j];
stack<int> s;
fo(j,1,m+1)
{
if (a[i][j] == 1) f[j]++; else f[j] = 0;
if (s.empty() || f[j] >= f[s.top()]) s.push(j); else
{
while (f[s.top()] > f[j])
{
rfar[s.top()] = j - 1;
s.pop();
if (s.empty()) break;
}
s.push(j);
}
}
fo(j,1,m) f[j] -= a[i][j];
while (!s.empty()) s.pop();
fd(j,m,0)
{
if (a[i][j] == 1) f[j]++; else f[j] = 0;
if (s.empty() || f[j] >= f[s.top()]) s.push(j); else
{
while (f[s.top()] > f[j])
{
lfar[s.top()] = j + 1;
s.pop();
if (s.empty()) break;
}
s.push(j);
}
}
fo(j,1,m)
if (a[i][j] == 1)
{
l = lfar[j]; r = rfar[j]; len = r - l + 1;
//if (lfar[j] == 0 || rfar[j] == 0) continue;
if (b[l][r] == i) continue; b[l][r] = i;
if (len != sum[r] - sum[l-1]) res++;//{res++; cout<
}
}
cout<<res<<endl;
return 0;
}
考虑每种数字的贡献
某种数字一定会把这个序列分割成若干区间,对这些区间中的子区间的贡献为0,其他区间的贡献都为1
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int n,x,s,i,j;
long long num,len;
int a[100005];
vector<int> v[100005];
int main()
{
scanf("%d",&n);
fo(i,1,n) scanf("%d",&a[i]);
fo(i,1,n) v[a[i]].push_back(i);
num = 0;
fo(i,1,100000)
if (v[i].size())
{
v[i].push_back(n+1);
num = num + 1ll * n * (n + 1) / 2;
s = 1;
for (j = 0;j < v[i].size();j++)
{
x = v[i][j];
len = x - s;
num = num - len * (len + 1) / 2;
s = x + 1;
}
}
cout<<num<<endl;
return 0;
}
把体积离散化,那么最后的答案一定是这些若干段离散后的区间
然后对这些离散后的体积建立线段树,对于每一条边,在线段树上打上标记,这个标记的打法是类似于区间覆盖操作的,即在 [ 1 , 5 ] [1,5] [1,5]上打了标记就不用在 [ 1 , 3 ] [1,3] [1,3]和 [ 4 , 5 ] [4,5] [4,5]上打标记了
然后我们发现,对于任何一个叶子结点,从这个点到根结点上的所有标记,实际上就是这个区间所能够通过的边,那我们只需要判断 1 1 1能否走到 n n n就可以了
具体实现可以用dfs,每经过一个点就把所有的标记加入并查集,然后判断一下 1 1 1和 n n n在不在同一个并查集里就可以了,注意一下这里要用到回滚操作,所以并查集不能用路径压缩而要用按秩合并
时间复杂度分析:
打标记:实际上和区间覆盖的时间复杂度是一样的,最多只会打上log个标记, O ( n l o g n ) O(nlogn) O(nlogn)
dfs:遍历一遍, O ( n l o g n ) O(nlogn) O(nlogn)
并查集:按秩合并的理论复杂度是log的,即每个标记的复杂度都是 O ( l o g n ) O(logn) O(logn),总共有 O ( n l o g n ) O(nlogn) O(nlogn)个标记, O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define N 100005
using namespace std;
struct www{int u,v,l,r;} e[N];
struct tree{int l,r;} tree[N*16];
vector<int> pt[N*16];
int f[N*2],fa[N],len[N],s1[N*20],s2[N*20],s3[N*20],L[N*4],R[N*4];
int k,n,m,i,res,snum,tot;
void build(int rt,int l,int r)
{
if (l == r) {tree[rt].l = L[l]; tree[rt].r = R[l]; return;}
int mid = (l + r) >> 1;
build(rt<<1,l,mid); build(rt<<1|1,mid+1,r);
tree[rt].l = tree[rt<<1].l; tree[rt].r = tree[rt<<1|1].r;
}
void update(int rt,int l,int r,int i,int L,int R)
{
if (L <= tree[rt].l && tree[rt].r <= R)
{
pt[rt].push_back(i);
return;
}
int mid = (l + r) >> 1;
if (L <= tree[rt<<1].r) update(rt<<1,l,mid,i,L,R);
if (tree[rt<<1|1].l <= R) update(rt<<1|1,mid+1,r,i,L,R);
}
int find(int x) {if (fa[x] == x) return x; else return find(fa[x]);}
void dfs(int rt,int l,int r)
{
int i,t,fa1,fa2;
for (i = 0;i < pt[rt].size(); i++)
{
t = pt[rt][i];
fa1 = find(e[t].u); fa2 = find(e[t].v);
if (fa1 == fa2) continue;
if (len[fa1] < len[fa2])
{
fa[fa1] = fa2;
snum++; s1[snum] = fa1; s2[snum] = rt; s3[snum] = -1;
} else
if (len[fa1] > len[fa2])
{
fa[fa2] = fa1;
snum++; s1[snum] = fa2; s2[snum] = rt; s3[snum] = -1;
} else
{
fa[fa1] = fa2; len[fa2]++;
snum++; s1[snum] = fa1; s2[snum] = rt; s3[snum] = fa2;
}
}
if (find(1) == find(n))
{
res += tree[rt].r - tree[rt].l + 1;
} else
if (l != r)
{
int mid = (l + r) >> 1;
dfs(rt<<1,l,mid); dfs(rt<<1|1,mid+1,r);
}
while (snum > 0 && s2[snum] == rt)
{
fa[s1[snum]] = s1[snum];
if (s3[snum] != -1) len[s3[snum]]--;
snum--;
}
}
int main()
{
scanf("%d%d",&n,&m);
fo(i,1,m)
{
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].l,&e[i].r);
f[++k] = e[i].l; f[++k] = e[i].r;
}
sort(f+1,f+k+1);
k = unique(f+1,f+k+1)-(f+1);
fo(i,1,k)
{
if (f[i-1]+1 <= f[i]-1) {tot++; L[tot] = f[i-1]+1; R[tot] = f[i]-1;}
tot++; L[tot] = R[tot] = f[i];
}
if (f[k] != 1000000000) {tot++; L[tot] = f[k] + 1; R[tot] = 1000000000;}
//fo(i,1,tot) cout<
build(1,1,tot);
fo(i,1,m) update(1,1,tot,i,e[i].l,e[i].r);
fo(i,1,n) fa[i] = i,len[i] = 1;
dfs(1,1,tot);
//fo(i,1,n) cout<
cout<<res<<endl;
return 0;
}
用栈模拟,不断从左往右把元素压入栈,如果栈顶的三个元素相同则弹出
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
char ch[100005],stck[100005];
int n,p,i,res;
int main()
{
scanf("%s",ch+1);
n = strlen(ch+1);
p = 0;
fo(i,1,n)
{
p++; stck[p] = ch[i];
if (p >= 3)
{
if (stck[p] == stck[p-1] && stck[p] == stck[p-2])
{
p--; p--; p--;
res++;
}
}
}
cout<<res<<endl;
return 0;
}