[Contest on 2021.10.3] 眼珠要爆了

消失的运算符

题目描述

给定一个长度为 (n) 的合法表达式,并且只出现括号,减号和在 ([1,9]) 之间的数字。其中减号代表运算符的空位,设有 (m) 个。
给定 (k),你要把 (k) 个减号替换成加号,把 (m-k) 个减号替换成乘号。求所有本质不同的替换方案表达式的和对 (10^9+7) 取模后的值。

(1le nle 10^5,0le mle 2500)

解法

( ext{Subtask 1}):没有括号

因为要写 (mathcal O(m^2)) 的算法,然后加了前缀和一直调调调,后来发现先开始的 (mathtt{dp}) 方程有问题… 都是泪啊。

(mathtt{dp}) 转移也很简单,首先找到减号的位置序列 (p),令 (dp_{i,j})(1)(p_i-1) 之间的字符组成的所有表达式中,有 (j) 个加号的权值和。除此之外需要定义辅助数组 (f_{i,j},g_{i,j}),它们的状态和前文类似,但具体分别代表方案数与一坨奇怪的东西(后面再讲。

那么就有转移:

[dp_{i,j}=sum_{k=1}^{i-1}dp_{k,j-1}+mul(k+1,i)cdot f_{k,j-1} ]

由于 (mul(k+1,i)) 这一项很难搞,我们考虑将其拆分成两个部分:令 (pre_i) 为前缀积,(inv_i)(pre_i) 的逆元。转移就可以变成:

[dp_{i,j}=sum_{k=1}^{i-1}dp_{k,j-1}+pre_icdot sum_{k=1}^{i-1}inv_kcdot f_{k,j-1} ]

[g_{i,j}=sum_{k=1}^{i-1}inv_kcdot f_{k,j} ]

那么就有:

[dp_{i,j}=sum_{k=1}^{i-1}dp_{k,j-1}+pre_icdot g_{i-1,j-1} ]

就做完了。代码太长了,就放链接吧。

( ext{Link.})

( ext{Subtask 2}):有括号

用这个序列构造一棵括号树,相同深度的点所代表的区间就转化成上个子任务的 "数字"。

但是求几个区间的 (mul()) 似乎比较困难,我们不妨换一个 (mathtt{dp}) 定义:令 (f_{i,j}) 为括号树上的点 (i)(它对应原序列的一段区间),有 (j) 个加号的权值和,(g_{i,j}) 为括号树上的点 (i),有 (j) 个加号的最后一段乘积和,(cnt_{i,j}) 就是方案数。

具体转移的时候,假设需要算出 (i)(mathtt{dp}) 值,我们先不将 (f_{i,j}) 加上最后一段乘积和。算完再加回去。

(i) 统治的区间划分成几个上个子任务的 "数字",先分别递归,返回时合并即可,这类似于 树形背包,复杂度是 (mathcal O(n^2)) 的。实现时注意数字 ([l,r]) 对应减号 ([l,r)),在算大小的时候也要考虑。

代码

( ext{Link.})

数据恢复

题目描述

给定一棵 (n) 个点的树,对于所有 (2le ile n),它的父亲为 (f_i),每个点有系数 (a_i,b_i)

你需要求出一个的排列,满足对于 (2le ile n)(f_i) 都在 (i) 出现前出现。

这个排列的代价为:

[sum_{i=1}^n left(b_{p_i}cdot sum_{j=i+1}^n a_{p_j} ight) ]

求最大代价。

(nle 3 imes 10^5)

解法

菊花图是没有 (f_i) 限制的情况,这提醒我们先思考没有限制怎么做。考虑相邻两个数 (i,j(i<j)),当它们的位置交换时 (delta=b_ja_i-b_ia_j)。假设交换前更优,就可以得出 (frac{a_i}{b_i}<frac{a_j}{b_j}),令 (v_i=frac{a_i}{b_i}),直接升序排然后选即可。

加上限制之后,还是升序排序,可以发现选 (f_u) 之后直接选 (u) 肯定是最优的。而且,由乘法分配律,我们惊喜地发现这两个点的信息是可以合并的!用一个堆维护升序,就是 (mathcal O(nlog n)) 的。

另一道类似的题是 牛半仙的魔塔

由于奇特的数据范围,你会发现牛半仙每次减少的血量都是正数,所以我们只需要最小化减少的血量即可(因为不会回血,所以末状态一定是血量最低时刻)。

最小化减少的血量似乎挺复杂的,其实还有一个性质:每个怪物存活的轮数是固定的。从而每个怪物对牛半仙的伤害是固定的(如果防御不增加的话)。所以最大化防御值减伤的效果即可。假设怪物 (i) 的存活轮数是 (r_i),增加防御为 (w_i)。还是考虑相邻两个数 (i,j(i<j)),当它们的位置交换时 (delta=w_jr_i-w_ir_j)(减伤的效果)。假设交换前更优,就可以得出 (frac{w_i}{r_i}>frac{w_j}{r_j}),令 (v_i=frac{w_i}{r_i}),这就变成了上面的问题。实际实现 还要模拟一下打怪。

代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f |= (s=='-');
	while(s>='0' and s<='9')
		x = (x<<1)+(x<<3)+(s^48),
		s = getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

#include <queue>
using namespace std;
typedef long long ll;

const int maxn = 3e5+5;

int n,fa[maxn],f[maxn];
int a[maxn],b[maxn],siz[maxn];
struct node {
	double v;
	int id,sz;
	node() {}
	node(double V,int ID,int SZ) {
		v=V,id=ID,sz=SZ;
	}
	
	bool operator < (const node &t) const {
		return v>t.v;
	}
};
priority_queue <node> q;

void init() {
	for(int i=1;i<=n;++i)
		f[i]=i,siz[i]=1;
}

int Find(int x) {
	return x==f[x]?x:f[x]=Find(f[x]);
}

int main() {
	n=read(9); init();
	for(int i=2;i<=n;++i)
		fa[i]=read(9);
	for(int i=1;i<=n;++i) {
		a[i]=read(9),
		b[i]=read(9);
		if(i^1)
			q.push(node(1.0*a[i]/b[i],i,1));
	}
	int x,y; ll ans=0;
	while(!q.empty()) {
		node t=q.top(); q.pop();
		if(t.sz^siz[t.id])	
			continue;
		x=Find(fa[t.id]);
		y=Find(t.id);
		if(x==y) continue;
		ans += 1ll*b[x]*a[y];
		b[x]+=b[y],a[x]+=a[y];
		siz[f[y]=x]+=siz[y];
		if(x^1)
			q.push(node(1.0*a[x]/b[x],x,siz[x]));
	}
	print(ans,'
');
	return 0;
}

下落的小球

题目描述

有一棵 (n) 个点的树,根为 (1),每个点都有一个初始为 (1) 的标记值 (s_i)。对点 (x) 进行操作表示把 (x) 的祖先中深度最小并且 (s_i=1)(i) 置为 (0)

每个 叶子 有一个操作次数 (a_i),问有多少种不同的操作序列使得最后标记值全为 (0)

(nleq 10^6)(sum a_i=n),保证若 (i) 非叶子则 (a_i=0)

解法

(b_i)(i) 子树的 (a_i) 之和,(s_i) 为子树大小。令 (r_i=b_i-s_i),如果任意 (i) 均满足 (r_ige 0),这个 (a) 序列就是可以实现的。具体就是若 (r_i<0),那么这棵子树不会被消耗完;反之,由于题目中保证了 (sum a_i=n),所以不会出现 "供不应求" 的情况。

对于点 (i),考虑何时它的标记值为 (0)。走 (r_i) 步后,子树中每个点的标记值均为 (1),且此时子树操作次数也变成 (s_i),显然不再需要外部供给了。所以 (r_i+1) 步时 (i) 的标记值就是 (0) 了。将 (r_i) 步之内称为 (S) 部,剩余步称为 (T) 部。

套路地,我们从子树到根合并某节点儿子 (x,y) 的操作序列,令点 (i) 的操作序列方案数为 (dp_i)。由于共用父亲,当 (x) 失去外部供给时 (y) 还有外部供给显然是不合理的,所以 (x,y)(S,T) 部一定是 (S_x,S_y) 自由组合,(T_x,T_y) 自由组合再将 (S',T')顺序 拼接。这就是个组合数嘛!

具体可以这样转移:

for(v in Son_u) {
    s[u]+=s[v],r[u]+=r[v];
    dp[u] = dp[u]*dp[v]/(s[v]!)/(r[v]!);
}
dp[u] = dp[u]*(s[u]-1)!*(r[u]+1)!;

dp[u]*dp[v] 是选出 各自 排列顺序,剩下一坨就是组合数,化简发现中间一段可以抵消。由于 (u) 不是叶子,所以 (sum r_v=r_u+1)

古老的序列问题

题目描述

给定长度为 (n) 的序列 (s)。你需要回答 (m) 个询问,每次询问给出 (L,R),你需要计算以下式子:

[left (sum_{l=L}^R sum_{r=l}^R cost(l,r) ight)mod 10^9+7 ]

其中 (cost(l,r))([l,r]) 中最大值和最小值的乘积。

(1le n,mle 10^5,1le s_ile 10^9)

解法

( ext{Subtask 1})(s_ile s_{i+1})

问题相当于求 ([L,R]) 中两两数字的乘积。可以预处理出 (suf_i=s_icdot sum_{j=i}^n s_i)。那么

[ ext{Ans}=sum_{i=L}^R suf_i -sum_{i=L}^Rs_icdot sum_{i=R+1}^n s_i ]

另外中间还有几档部分分,但是我不会~

( ext{Subtask 2})(mathcal O(nlog^2 n))

感觉这种离线询问题很多都可以用 ( m cdq) 分治搞一个最优/次优解出来,以后不妨往上面靠靠… 当然这道题就是打死我也想不到该怎么搞

一个粗糙的思路是,将完全在 ([l, ext{mid}))(( ext{mid},r]) 的询问扔给儿子,自己处理经过 ( ext{mid})(cost(l',r'))

( m mid) 开始向左移左指针,按这个顺序对右区间贡献(用数据结构维护)。这样我们将询问按 (L) 从大到小排序,就可以当左指针到 (L_i) 时,更新询问 (i)。我们把经过 ( m mid) 的区间划分成四种情况:

  1. (min,max) 都在左区间。
  2. (max) 在左区间。
  3. (min) 在左区间。
  4. (min,max) 都在右区间。

由于左指针左移时,左区间的 (min,max) 一定趋向更优,所以可以用两个指针维护右区间满足 "(min,max) 都在左区间" 的右端点的边界 ( ext{MX}, ext{MN}),这是递增的。然后就可以得到四种情况的区间。

讲了这么多,究竟该如何维护 (cost()) 呢?建出四棵线段树分别代表四种情况(维护右区间),比如,情况二初始时就是给 (( ext{mid},r])(v_j) 赋值为 (min_{i= ext{mid}+1}^j s_j),在移动左指针时,找到情况二对应的区间,然后传递当前左区间的 (max),区间内每个点的答案就需要累加 (v_jcdot max)

感觉每一步都好妙啊。

( ext{Subtask 3})(mathcal O(nlog n))

事实上,这种 "给定区间求所有子序列信息" 的题目有一个通用的思路:求历史的和。

比如此题,我们右移右端点到 (i),此时左端点 ([1,i]) 表示 (cost({1,2,...,i},i))。当 (i+1) 时,(cost({1,2,...,i},i)) 就变成了历史值。对于询问 ((l,r)),我们就需要求出右端点为 (r) 时,区间 ([l,r]) 的历史值。

可以用单调栈维护某个区间的 (min,max)(下文以 (min) 为例)。在右端点移动时,左端点对应的 (min) 可能会变化,如何修改?由于我们已经知道需要修改 (min) 的区间,所以直接乘上原本 (min) 的逆元即可。

具体可以用矩阵维护,令初始矩阵为 (egin{bmatrix}v&h end{bmatrix})。第一想法是将转移矩阵写成这样:(egin{bmatrix} c& ext{len}\ 0&1end{bmatrix})。然而修改时我们并不知道区间的 ( m len),所以将其设在初始矩阵的 (v) 处即可。需要注意的是,修改后没有将历史和更新,所以需要再乘一个矩阵更新历史和。

这就是 (mathcal O(nlog n)) 的,实测比上面那个做法快了一倍有余,只有 (600) 多毫秒。

代码

(mathcal O(nlog^2 n))

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 1e5+5;
const int mod = 1e9+7;

inline int inc(int x,int y) {
	return x+y>=mod?x+y-mod:x+y;
}

int n,m,s[maxn],f[maxn<<2];
int ans[maxn],L,R;
int a_[maxn],b_[maxn],c_[maxn],d_[maxn];
struct node {
	int l,r,id;
	node() {}
	node(int L,int R,int ID):l(L),r(R),id(ID) {}
	
	bool operator < (const node &t) const {
		return l>t.l;
	}
};
vector <node> q[maxn<<2];

class SegmentTree {
public:
	int la[maxn<<2],v[maxn<<2],sm[maxn<<2];
	
	void build(int o,int l,int r,int *t) {
		la[o]=sm[o]=0;
		if(l==r) return v[o]=t[l],void();
		int mid=(l+r)>>1;
		build(o<<1,l,mid,t);
		build(o<<1|1,mid+1,r,t);
		v[o]=inc(v[o<<1],v[o<<1|1]);
	}
	
	void update(int o,int k) {
		sm[o] = inc(sm[o],1ll*v[o]*k%mod);
		la[o] = inc(la[o],k);
	}
	
	void pushDown(int o) {
		if(!la[o]) return;
		update(o<<1,la[o]);
		update(o<<1|1,la[o]);
		la[o]=0;
	}
	
	void modify(int o,int ql,int qr,int k,int l=L,int r=R) {
		if(l>=ql and r<=qr)
			return update(o,k),void();
		int mid=(l+r)>>1;
		pushDown(o);
		if(ql<=mid) modify(o<<1,ql,qr,k,l,mid);
		if(qr>mid) modify(o<<1|1,ql,qr,k,mid+1,r);
		sm[o] = inc(sm[o<<1],sm[o<<1|1]);
	}
	
	int ask(int o,int lim,int l=L,int r=R) {
		if(r<=lim) return sm[o];
		int mid=(l+r)>>1,ret;
		pushDown(o);
		ret=ask(o<<1,lim,l,mid);
		if(mid<lim) 
			ret=inc(ret,ask(o<<1|1,lim,mid+1,r));
		return ret;
	}
} A,B,C,D;

void dicon(int o,int l,int r) {
	if(l==r) {
		f[o] = 1ll*s[l]*s[l]%mod;
		for(auto i:q[o])
			ans[i.id] = inc(ans[i.id],f[o]);
		return;
	}
	int mid=(l+r)>>1;
	vector <node> cur;
	for(auto i:q[o]) {
		if(i.l<=mid and i.r>mid and !(i.l==l and i.r==r))
			cur.push_back(i);
		if(i.r<=mid) q[o<<1].push_back(i);
		else if(i.l>mid) q[o<<1|1].push_back(i);
		else if(!(i.l==l and i.r==r)) {
			q[o<<1].push_back(node(i.l,mid,i.id));
			q[o<<1|1].push_back(node(mid+1,i.r,i.id));
		}
	}
	sort(cur.begin(),cur.end());
	int mn=mod,mx=0;
	for(int i=mid+1;i<=r;++i) {
		mn=min(mn,s[i]);
		mx=max(mx,s[i]);
		a_[i]=1; // all in left
		b_[i]=mn; // max in left
		c_[i]=mx; // min in left
		d_[i]=1ll*mx*mn%mod;
		// all in right
	}
	L=mid+1,R=r;
	A.build(1,mid+1,r,a_); B.build(1,mid+1,r,b_);
	C.build(1,mid+1,r,c_); D.build(1,mid+1,r,d_);
	int p=0,siz=cur.size(),MN=mid,MX=mid;
	mn=mod,mx=0;
	for(int i=mid;i>=l;--i) {
		mn=min(mn,s[i]);
		mx=max(mx,s[i]);
		while(MN<r and s[MN+1]>=mn) ++MN;
		while(MX<r and s[MX+1]<=mx) ++MX;
		if(mid+1<=MN and mid+1<=MX)
			A.modify(1,mid+1,min(MN,MX),1ll*mn*mx%mod);
		if(MN<MX) B.modify(1,MN+1,MX,mx);
		if(MN>MX) C.modify(1,MX+1,MN,mn);
		if(MX+1<=r and MN+1<=r)
			D.modify(1,max(MN,MX)+1,r,1);
		while(p<siz and cur[p].l==i) {
			ans[cur[p].id] = inc(
				ans[cur[p].id],
				inc(
					inc(A.ask(1,cur[p].r),B.ask(1,cur[p].r)),
					inc(C.ask(1,cur[p].r),D.ask(1,cur[p].r))
				)
			);
			++p;
		}
	}
	f[o] = inc(inc(A.ask(1,r),B.ask(1,r)),
			   inc(C.ask(1,r),D.ask(1,r)));
	dicon(o<<1,l,mid),dicon(o<<1|1,mid+1,r);
	f[o] = inc(f[o],inc(f[o<<1],f[o<<1|1]));
	for(auto i:q[o])
		if(i.l==l and i.r==r)
			ans[i.id] = inc(ans[i.id],f[o]);
}

int main() {
	n=read(9),m=read(9);
	for(int i=1;i<=n;++i)
		s[i]=read(9);
	int x,y;
	for(int i=1;i<=m;++i)
		x=read(9),y=read(9),
		q[1].push_back(node(x,y,i));
	dicon(1,1,n);
	for(int i=1;i<=m;++i)
		print(ans[i],'
');
	return 0;
}

(mathcal O(nlog n))

#pragma GCC optimize(2)
#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f |= (s=='-');
	while(s>='0' and s<='9')
		x = (x<<1)+(x<<3)+(s^48),
		s = getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-');
		write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
}

#include <vector>
using namespace std;

const int maxn = 1e5+5;
const int mod = 1e9+7;

inline int inc(int x,int y) {
	return x+y>=mod?x+y-mod:x+y;
}

int n,m,s[maxn],ans[maxn];
int mn[maxn],mn_l,mx[maxn],mx_l;
struct Q {
	int l,id;
	Q() {}
	Q(int L,int ID):l(L),id(ID) {}
};
vector <Q> q[maxn];
struct mat {
	int a[2][2];
	mat() {
		a[0][0]=a[0][1]=a[1][0]=a[1][1]=0;
	}
	
	mat operator * (const mat &t) const {
		mat r;
		for(int i=0;i<2;++i)
		for(int j=0;j<2;++j)
			if(a[i][j])
				for(int k=0;k<2;++k)
					r.a[i][k]=inc(
						r.a[i][k],
						1ll*a[i][j]*t.a[j][k]%mod
					);
		return r;
	}
	
	bool operator == (const mat &t) const {
		for(int i=0;i<2;++i)
		for(int j=0;j<2;++j)
			if(a[i][j]^t.a[i][j])
				return 0;
		return 1;
	}
	
	void Print() {
		puts("----------");
		for(int i=0;i<2;++i) {
			for(int j=0;j<2;++j)
				print(a[i][j],' ');
			puts("");
		}
	}
} e,upd,k,t[maxn<<2],la[maxn<<2];

int inv(int x,int y=mod-2) {
	int r=1;
	while(y) {
		if(y&1) r=1ll*r*x%mod;
		x=1ll*x*x%mod; y>>=1;
	}
	return r;
}

void initMat() {
	e.a[0][0]=e.a[1][1]=1;
	upd.a[0][0]=upd.a[1][1]=upd.a[0][1]=1;
}

void getMat(int x) {
	k.a[0][0]=x,k.a[1][1]=1;
	k.a[0][1]=k.a[1][0]=0;
}

void build(int o,int l,int r) {
	t[o].a[0][0]=r-l+1;
	la[o]=e; // important!
	if(l==r) return;
	int mid=l+r>>1;
	build(o<<1,l,mid);
	build(o<<1|1,mid+1,r);
}

void pushDown(int o) {
	if(la[o]==e) return;
	t[o<<1]=t[o<<1]*la[o];
	t[o<<1|1]=t[o<<1|1]*la[o];
	la[o<<1]=la[o<<1]*la[o];
	la[o<<1|1]=la[o<<1|1]*la[o];
	la[o]=e;
}

void modify(int o,int l,int r,int L,int R) {
	if(l>=L and r<=R) {
		t[o]=t[o]*k; la[o]=la[o]*k;
		return;
	}
	int mid=l+r>>1;
	pushDown(o);
	if(L<=mid) modify(o<<1,l,mid,L,R);
	if(R>mid) modify(o<<1|1,mid+1,r,L,R);
	t[o].a[0][0]=inc(t[o<<1].a[0][0],t[o<<1|1].a[0][0]);
	t[o].a[0][1]=inc(t[o<<1].a[0][1],t[o<<1|1].a[0][1]);
}

int ask(int o,int l,int r,int L,int R) {
	if(l>=L and r<=R) return t[o].a[0][1];
	int mid=l+r>>1,ret=0;
	pushDown(o);
	if(L<=mid) ret=ask(o<<1,l,mid,L,R);
	if(R>mid) ret=inc(ret,ask(o<<1|1,mid+1,r,L,R));
	return ret;
}

int main() {
	n=read(9),m=read(9);
	for(int i=1;i<=n;++i)
		s[i]=read(9);
	int x,y;
	for(int i=1;i<=m;++i)
		x=read(9),y=read(9),
		q[y].push_back(Q(x,i));
	initMat(); build(1,1,n); 
	for(int i=1;i<=n;++i) {
		while(mn_l and s[mn[mn_l]]>=s[i]) { 
			getMat(inv(s[mn[mn_l--]])); 
			modify(1,1,n,mn[mn_l]+1,mn[mn_l+1]);
		}
		while(mx_l and s[mx[mx_l]]<=s[i]) {
			getMat(inv(s[mx[mx_l--]]));
			modify(1,1,n,mx[mx_l]+1,mx[mx_l+1]);
		}
		getMat(s[i]);
		modify(1,1,n,mx[mx_l]+1,i);
		modify(1,1,n,mn[mn_l]+1,i);
		k=upd;
		modify(1,1,n,1,i);
		mx[++mx_l]=mn[++mn_l]=i;
		for(int j=0;j<q[i].size();++j)
			ans[q[i][j].id]=ask(1,1,n,q[i][j].l,i);
	}
	for(int i=1;i<=m;++i) print(ans[i],'
');
	return 0;
}
原文地址:https://www.cnblogs.com/AWhiteWall/p/15367511.html