消失的运算符
题目描述
给定一个长度为 (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}),它们的状态和前文类似,但具体分别代表方案数与一坨奇怪的东西(后面再讲。
那么就有转移:
由于 (mul(k+1,i)) 这一项很难搞,我们考虑将其拆分成两个部分:令 (pre_i) 为前缀积,(inv_i) 是 (pre_i) 的逆元。转移就可以变成:
令
那么就有:
就做完了。代码太长了,就放链接吧。
( 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)),在算大小的时候也要考虑。
代码
数据恢复
题目描述
给定一棵 (n) 个点的树,对于所有 (2le ile n),它的父亲为 (f_i),每个点有系数 (a_i,b_i)。
你需要求出一个的排列,满足对于 (2le ile n),(f_i) 都在 (i) 出现前出现。
这个排列的代价为:
求最大代价。
(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),你需要计算以下式子:
其中 (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{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) 的区间划分成四种情况:
- (min,max) 都在左区间。
- (max) 在左区间。
- (min) 在左区间。
- (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;
}