题目整理

1

link: 匹配子串 - 题目详情 - 思+学堂

题意

给定一个字符串 s,其中 si{(,),?}。定义 g(l,r):如果能将 s[l..r] 内的 ? 替换成 () 使得 s[l..r] 成为一个合法的括号序列,则 f(l,r)=1;否则 f(l,r)=0

q 次询问,每次询问 lxyrf(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)st?。具体的原因是 g(x)h(x) 均非负,且两个函数都在整数域上连续。

找到了这个 t 之后,我们便让 s[l..t] 中的 ? 变成 (s[t..r] 中的所有 ? 变成 )。可以验证这样会满足条件。


定义 nxti 为最大的一个 x 使得 s[i..x] 满足前缀的 ( 数量 + ? 数量 - ) 数量非负;prei 的定义类似。因此,我们可以归纳出 f(l,r)=1 的充要条件:

  1. rnxtl
  2. lprer
  3. rl+10(mod2)

接下来,我们可以用扫描线解决这个问题。

满足条件的,便是中间的绿色区域。建立线段树,每个节点维护两个信息 ab,和以下操作:

  1. 区间 a 标记加一。
  2. 区间 bb+a
  3. 询问区间中 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=1n,你需要求出长度为 k 的子序列中,逆序对最小值以及总共出现了几次最小值。

做法

首先,因为 n40,所以首先考虑折半搜索。

但是,简单思索后发现折半搜索做不了。折半搜索能做的前提:在搜索右边的时候能快速找到左边的最小值。但显然,左边的状态数极大,我们也没有方法快速在所有集合中找到最小值。

因此考虑缩减状态。因为在右边统计状态的时候,我们只关心的是当前状态有多少个比右边的数大。所以说,我们完全可以不存储当前选数的集合,只存储当前选的数和右边所有数的大小关系。

当然,缩减完状态之后,可以直接 DP。时间复杂度为 O(3n3)

但是,我们也可以重新考虑回折半搜索。设前 B 个暴力枚举,后 B 个在左边压缩后的状态上暴力枚举。

时间复杂度为 O(2B(nB)+C2nB)。其中 C 大概是 3×105

实测取 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;
}

posted @   linxuanrui  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示