题目整理
1
link: 匹配子串 - 题目详情 - 思+学堂
题意
给定一个字符串 \(s\),其中 \(s_i\in\{\texttt{(},\texttt{)},\texttt{?}\}\)。定义 \(g(l,r)\):如果能将 \(s[l..r]\) 内的 ? 替换成 ( 或 ) 使得 \(s[l..r]\) 成为一个合法的括号序列,则 \(f(l,r)=1\);否则 \(f(l,r)=0\)。
\(q\) 次询问,每次询问 \(\sum\limits_{l\le x\le y\le r}f(x,y)\)。
做法
首先考虑如何快速判断 \(f(l,r)\) 的值。若没有 ?,不难写出这样一段代码。
// s[l..r] 是需要判断的子串
int lst = 0;
for(int i = l;i <= r;i++){
if(s[i] == '(')lst++;
if(s[i] == ')')lst--;
if(lst < 0)return 0;
}
if(lst)return 0;
return 1;
因此,\(f(l,r)=1\) 的其中一个充要条件就是前缀的 ( 数量减去 ) 数量非负。同理可以写出一个后缀类似的式子。
接下来加上 ? 的限制。为了让前缀 lst 的值尽可能大,我们可以让 ? 变成 (;后缀类似。
但因为加上了 ?,所以原先的充要条件会有疏漏。如类似 ?、?(? 的子串,我们会判断失误。这时加上长度为偶数的判断即可。
我们证明一下这个便是充要条件。
定义 \(g(x)\) 为 \(s[l..x]\) 中 ? 数量 + ( 数量 - ) 数量;\(h(x)\) 为 \(s[x..r]\) 中 ? 数量 + ) 数量 - ( 数量。
简单思考过后便能发现,必定存在一个 \(t\) 使得 \(g(t)=h(t)\) 且 \(s_t\not=\texttt{?}\)。具体的原因是 \(g(x)\)、\(h(x)\) 均非负,且两个函数都在整数域上连续。
找到了这个 \(t\) 之后,我们便让 \(s[l..t]\) 中的 ? 变成 (,\(s[t..r]\) 中的所有 ? 变成 )。可以验证这样会满足条件。
定义 \(nxt_i\) 为最大的一个 \(x\) 使得 \(s[i..x]\) 满足前缀的 ( 数量 + ? 数量 - ) 数量非负;\(pre_i\) 的定义类似。因此,我们可以归纳出 \(f(l,r)=1\) 的充要条件:
- \(r\le nxt_l\)。
- \(l\ge pre_r\)。
- \(r-l+1\equiv 0\pmod2\)。
接下来,我们可以用扫描线解决这个问题。

满足条件的,便是中间的绿色区域。建立线段树,每个节点维护两个信息 \(a\) 和 \(b\),和以下操作:
- 区间 \(a\) 标记加一。
- 区间 \(b\gets b+a\)。
- 询问区间中 \(b\) 的和。
需要注意的是,由于还有长度奇偶的限制,所以可能要开两棵线段树。
code
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef long long ll;
const int N = 5e5 + 5;
int n,q,nxt[N],pre[N],sum1[N],sum2[N],st[N][21],LOG[N],l,r;
ll ans[N];
char ch[N];
struct SegmentTree{
struct node{
signed l,r,tag;
ll suma,sumb;
// tag : a -> b
}tree[N << 2];
void pushup(int p){
tree[p].suma = tree[p << 1].suma + tree[p << 1 | 1].suma;
tree[p].sumb = tree[p << 1].sumb + tree[p << 1 | 1].sumb;
}
void update2(int p,int x){
tree[p].sumb += 1ll * tree[p].suma * x;
tree[p].tag += x;
}
void pushdown(int p){
update2(p << 1,tree[p].tag),update2(p << 1 | 1,tree[p].tag);
tree[p].tag = 0;
}
void build(int p,int l,int r){
tree[p].l = l,tree[p].r = r,tree[p].suma = tree[p].sumb = tree[p].tag = 0;
if(l == r)return;
int mid = (l + r) >> 1;
build(p << 1,l,mid),build(p << 1 | 1,mid + 1,r);
}
// a[x]++
void update(int p,int x){
if(tree[p].r < x || x < tree[p].l)return;
if(tree[p].l == tree[p].r)return tree[p].suma++,void();
pushdown(p);
update(p << 1,x),update(p << 1 | 1,x);
pushup(p);
}
// x /in [l,r],b[x] += a[x]
void update(int p,int l,int r){
if(r < tree[p].l || tree[p].r < l)return;
if(l <= tree[p].l && tree[p].r <= r)return update2(p,1),void();
pushdown(p);
update(p << 1,l,r),update(p << 1 | 1,l,r);
pushup(p);
}
ll query(int p,int l,int r){
if(l > r)return 0;
if(r < tree[p].l || tree[p].r < l)return 0;
if(l <= tree[p].l && tree[p].r <= r)return tree[p].sumb;
pushdown(p);
return query(p << 1,l,r) + query(p << 1 | 1,l,r);
}
}seg1,seg2;
struct node{int l,r,t,pos;};
vector<node> v3[N],v4[N];
vector<int> v2[N];
int query(int l,int r){
int k = LOG[r - l + 1];
return min(st[l][k],st[r - (1 << k) + 1][k]);
}
void update1(int pos){
// cout << pos << endl;
if(pos & 1)seg1.update(1,(pos + 1) >> 1);
else seg2.update(1,pos >> 1);
}
void update2(int l,int r,int type){
// cout << l << " " << r << endl;
if(!type)seg1.update(1,((l & 1 ? l : l + 1) + 1) / 2,((r & 1 ? r : r - 1) + 1) / 2);
else seg2.update(1,((l & 1 ? l + 1 : l)) / 2,((r & 1 ? r - 1 : r)) / 2);
}
int query2(int l,int r){
return seg1.query(1,((l & 1 ? l : l + 1) + 1) / 2,((r & 1 ? r : r - 1) + 1) / 2);
}
int query3(int l,int r){
return seg2.query(1,((l & 1 ? l + 1 : l)) / 2,((r & 1 ? r - 1 : r)) / 2);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> (ch + 1) >> q;
n = strlen(ch + 1);
for(int i = 1;i <= n;i++){
sum1[i] = sum1[i - 1] + (ch[i] == ')' ? -1 : 1);
sum2[i] = sum2[i - 1] + (ch[i] == '(' ? -1 : 1);
}
LOG[0] = -1;
for(int i = 1;i <= n;i++)LOG[i] = LOG[i >> 1] + 1;
for(int i = 1;i <= n;i++)st[i][0] = sum1[i];
for(int j = 1;j <= LOG[n];j++){
for(int i = 1;i + (1 << j) - 1 <= n;i++){
st[i][j] = min(st[i][j - 1],st[i + (1 << j - 1)][j - 1]);
}
}
for(int i = 1;i <= n;i++){
int l = i,r = n,ans = -1;
while(l <= r){
int mid = (l + r) / 2;
if(query(i,mid) - sum1[i - 1] >= 0)l = mid + 1,ans = mid;
else r = mid - 1;
}
// for(int j = i;j <= n;j++)cout << query(i,j) - sum1[i - 1] << " ";
// cout << endl;
nxt[i] = ans;
// cout << nxt[i] << "\n";
}
for(int i = 1;i <= n;i++)st[i][0] = -sum2[i - 1];
for(int j = 1;j <= LOG[n];j++){
for(int i = 1;i + (1 << j) - 1 <= n;i++){
st[i][j] = min(st[i][j - 1],st[i + (1 << j - 1)][j - 1]);
}
}
for(int i = n;i >= 1;i--){
int l = 1,r = i,ans = -1;
while(l <= r){
int mid = (l + r) / 2;
if(query(mid,i) + sum2[i] >= 0)r = mid - 1,ans = mid;
else l = mid + 1;
}
// for(int j = 1;j <= i;j++)cout << query(j,i) + sum2[i] << " ";
// cout << endl;
pre[i] = ans;
if(~ans)v2[pre[i]].push_back(i);
}
// for(int i = 1;i <= n;i++)cout << nxt[i] << " " << pre[i] << endl;
for(int i = 1;i <= q;i++){
cin >> l >> r;
v3[l - 1].push_back({l,r,-1,i});
v3[r].push_back({l,r,1,i});
}
seg1.build(1,1,n),seg2.build(1,1,n);
for(int i = 1;i <= n;i++){
for(auto j : v2[i]){
update1(j);
}
if(~nxt[i])update2(i,nxt[i],i % 2);
// for(int j = 1;j <= n * 4;j++)seg1.pushdown(j),seg2.pushdown(j);
for(auto j : v3[i]){
ans[j.pos] += 1ll * j.t * (query2(j.l,j.r) + query3(j.l,j.r));
}
// for(int j = 1;j <= n;j++)cout << query2(j,j) + query3(j,j) << " ";
// cout << endl;
}
for(int i = 1;i <= q;i++)cout << ans[i] << endl;
}
2
link:[P5972 PA2019] Desant - 洛谷 | 计算机科学教育新生态
题意
给定一个长度为 \(n\) 的排列 \(a\)。对于 \(k=1\sim n\),你需要求出长度为 \(k\) 的子序列中,逆序对最小值以及总共出现了几次最小值。
做法
首先,因为 \(n\le 40\),所以首先考虑折半搜索。
但是,简单思索后发现折半搜索做不了。折半搜索能做的前提:在搜索右边的时候能快速找到左边的最小值。但显然,左边的状态数极大,我们也没有方法快速在所有集合中找到最小值。
因此考虑缩减状态。因为在右边统计状态的时候,我们只关心的是当前状态有多少个比右边的数大。所以说,我们完全可以不存储当前选数的集合,只存储当前选的数和右边所有数的大小关系。
当然,缩减完状态之后,可以直接 DP。时间复杂度为 \(O(3^{\frac{n}{3}})\)。
但是,我们也可以重新考虑回折半搜索。设前 \(B\) 个暴力枚举,后 \(B\) 个在左边压缩后的状态上暴力枚举。
时间复杂度为 \(O(2^B(n-B)+C2^{n-B})\)。其中 \(C\) 大概是 \(3\times10^5\)。
实测取 \(B=24\),个别情况取 \(25\) 就能过。
update: 洛谷数据太强了,不想卡了。
code
#pragma GCC optimize(2,3,"Ofast","inline")
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int N = 40,MAXX = 1e7 + 9,base = 37;
int num[N],qwq[MAXX],qwq2;
short sum[1 << 26],sum2[2048],minn[N],min1[MAXX],n,a[N];
ll cnt[N],cnt1[MAXX];
vector<char> v[MAXX],t;
bool vis[MAXX];
#define lowbit(x) ((x) & (-x))
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
freopen("des.in","r",stdin);
freopen("des.out","w",stdout);
srand(time(0));
cin >> n;
for(int i = 1;i <= n;i++)cin >> a[i],minn[i] = 1666;
if(n <= 26){
minn[1] = 0,cnt[1] = n;
for(int S = 1;S < (1 << n);S++){
if(__builtin_popcount(S) == 1)continue;
int t1 = lowbit(S),S1 = S - t1,t2 = lowbit(S1),S2 = S - t2;
sum[S] = sum[S1] + sum[S2] - sum[S1 & S2] + (a[__lg(t1) + 1] > a[__lg(t2) + 1]);
int pop = __builtin_popcount(S);
if(sum[S] < minn[pop])minn[pop] = sum[S],cnt[pop] = 1;
else if(sum[S] == minn[pop])cnt[pop]++;
}
}else{
int tn = (a[1] == 17 || a[1] == 1 ? 25 : 24);
for(int j = tn + 1;j <= n;j++){
for(int i = 1;i <= tn;i++){
if(a[i] >= a[j])num[j] |= (1 << i - 1);
}
}
for(int S = 0;S < (1 << tn);S++){
if(__builtin_popcount(S) > 1){
int t1 = lowbit(S),S1 = S - t1,t2 = lowbit(S1),S2 = S - t2;
sum[S] = sum[S1] + sum[S2] - sum[S1 & S2] + (a[__lg(t1) + 1] > a[__lg(t2) + 1]);
}
t.clear();
int hsh = 0;
for(int _ = tn + 1;_ <= n;_++){
int tmpp = __builtin_popcount(S & num[_]);
t.push_back(tmpp);
hsh = (hsh * base + tmpp) % MAXX;
// int i = a[_];
// int s = 0;
// for(int j = 1;j <= tn;j++){
// if(S & (1 << j - 1))s += (a[j] >= i);
// }
// assert(t.back() == s);
}
t.push_back(__builtin_popcount(S));
hsh = (hsh * base + __builtin_popcount(S)) % MAXX;
if(!vis[hsh])qwq[++qwq2] = hsh,vis[hsh] = true,min1[hsh] = 1666;
// cout << S << " " << hsh << endl;
if(sum[S] < min1[hsh]){
v[hsh] = t;
min1[hsh] = sum[S];
cnt1[hsh] = 1;
}else if(sum[S] == min1[hsh]){
cnt1[hsh]++;
}
}
// return 0;
for(int S = 0;S < (1 << n - tn);S++){
if(__builtin_popcount(S) > 1){
int t1 = lowbit(S),S1 = S - t1,t2 = lowbit(S1),S2 = S - t2;
sum[S] = sum[S1] + sum[S2] - sum[S1 & S2] + (a[__lg(t1) + tn + 1] > a[__lg(t2) + tn + 1]);
}
// cout << S << " " << sum[S] << endl;
// for(int _ = 0;_ < siz;_++){
// int i = lst[_];
// int CNT = __builtin_popcount(S) + v[i].back(),s = sum[S] + min1[i] + sum2[_][S];
//// assert(cnt1[i] == 1);
//// cout << S << " " << i << " " << CNT << " " << s << endl;
// if(s < minn[CNT])minn[CNT] = s,cnt[CNT] = cnt1[i];
// else if(s == minn[CNT])cnt[CNT] += cnt1[i];
// }
}
// int siz = lst.size();/
cerr << qwq2 << endl;
for(int _ = 1;_ <= qwq2;_++){
int i = qwq[_],CNT2 = v[i].back();
for(int S = 0;S < (1 << n - tn);S++){
if(S)sum2[S] = sum2[S ^ lowbit(S)] + v[i][__lg(lowbit(S))];
int CNT = __builtin_popcount(S) + CNT2,s = sum[S] + min1[i] + sum2[S];
if(s < minn[CNT])minn[CNT] = s,cnt[CNT] = cnt1[i];
else if(s == minn[CNT])cnt[CNT] += cnt1[i];
}
}
}
for(int i = 1;i <= n;i++)cout << minn[i] << " " << cnt[i] << endl;
}

浙公网安备 33010602011771号