赞
踩
div.2 视频讲解:BV19f4y1t7En
div.1 视频讲解:BV1io4y1C7Dd
有 n n n 只猫排成一排,标记为 1 1 1 到 n n n ,第 i i i 只猫位于位置 i i i 。 他们厌倦了整天在同一个地方打转,所以他们想重新安排自己,这样猫就不会像以前一样在同一个地方了。 他们也很懒惰,所以他们想尽量减少他们移动的总距离。 帮助他们决定在重新排序后每个位置应该放什么猫。
例如,如果有 3 3 3 只猫,这是一个有效的重新排序: [ 3 , 1 , 2 ] [3,1,2] [3,1,2] 。 没有猫在原来的位置。 猫移动的总距离为 1 + 1 + 2 = 4 1+1+2=4 1+1+2=4 ,因为猫 1 1 1 向右移动一位,猫 2 2 2 向右移动一位,而猫 3 3 3 向左移动两位。
贪心可得,相邻交换最优。
若
n
n
n 为偶数,则相邻奇偶位交换。
若
n
n
n 为奇数,则有三只猫轮换,剩余相邻奇偶位交换。
#include <bits/stdc++.h> typedef long long ll; using namespace std; int main() { int T,n,i; scanf("%d",&T); while(T--) { scanf("%d",&n); if(n%2) { printf("3 1 2"); for(i=4;i<=n;i+=2) printf(" %d %d",i+1,i); puts(""); } else { for(i=1;i<=n;i+=2) printf("%d %d ",i+1,i); puts(""); } } }
给定包含 n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 ) n(2 \leq n \leq 2 \cdot 10^5) n(2≤n≤2⋅105) 个不同元素的数组 a i ( 1 ≤ a i ≤ 2 ⋅ n ) a_i(1 \leq a_i \leq 2 \cdot n) ai(1≤ai≤2⋅n) ,求满足以下条件的 ( i , j ) (i,j) (i,j) 对:
对于每个
i
i
i ,暴力枚举所有
a
i
a_i
ai 的倍数然后判断即可,注意
i
≠
j
i \neq j
i=j 。
复杂度可以用调和级数证明为
O
(
n
⋅
l
o
g
(
n
)
)
O(n \cdot log(n))
O(n⋅log(n)) 。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int MAXN=100100; int a[MAXN]; map<int,int> mp; int main() { int T,n,i,j,k,ans; scanf("%d",&T); while(T--) { mp.clear(); scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&a[i]); mp[a[i]]=i; } ans=0; for(i=1;i<=n;i++) { for(k=a[i];k<=2*n;k+=a[i]) { j=mp[k/a[i]]; if(j&&j^i) ans+=(k==i+j); } } printf("%d\n",ans/2); } }
已知有一张包含
n
(
1
≤
n
≤
1
0
5
)
n(1 \leq n \leq 10^5)
n(1≤n≤105) 个节点的有向图。有向图上每条边有边权值,边权值可能为正也可能为负,不存在负环。
已知节点
1
1
1 到每个节点的最短距离
d
i
d_i
di ,求满足条件的图中,边权值总和最小为多少。
边权值总和最小时,必然只有一条正边,指向最远的节点。
同时,每个节点
i
i
i 会有负边指向其他更近的节点
j
j
j ,权值为
d
j
−
d
i
(
d
j
<
d
i
)
d_j-d_i(d_j < d_i)
dj−di(dj<di) 。
实现时,对所有距离排序后用前缀和处理即可。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int MAXN=100100; ll d[MAXN],sum[MAXN]; int main() { int T,i,n; ll mx,ans; scanf("%d",&T); while(T--) { scanf("%d",&n); for(i=1;i<=n;i++) scanf("%lld",&d[i]); sort(d+1,d+n+1); for(i=1;i<=n;i++) sum[i]=sum[i-1]+d[i]; ans=d[n]; for(i=1;i<=n;i++) ans+=sum[i-1]-sum[0]-(i-1)*d[i]; printf("%lld\n",ans); } }
给定一个包含
n
(
2
≤
n
≤
200
)
n(2 \leq n \leq 200)
n(2≤n≤200) 个节点的树,对树上的节点进行
1
1
1 到
n
n
n 编号。
一开始随机选择一个节点,标记为
1
1
1 。
之后每次随机选择一个与已被标记的节点相邻的未标记节点,进行标记。直到所有节点都被标记完。
设
a
i
a_i
ai 为节点
i
i
i 被标记的编号。求数列
a
i
a_i
ai 的逆序数对数量的期望。
考虑一对逆序数对
(
i
,
j
)
(
i
<
j
,
a
i
>
a
j
)
(i,j)(i < j,a_i>a_j)
(i,j)(i<j,ai>aj) 出现的概率
P
i
,
j
P_{i,j}
Pi,j,则易得总期望为
E
=
∑
i
=
1
n
∑
j
=
i
+
1
n
P
i
,
j
E=\sum_{i=1}^n{\sum_{j=i+1}^n{P_{i,j}}}
E=i=1∑nj=i+1∑nPi,j
以下图为例,假设
i
=
A
,
j
=
B
,
A
<
B
i=A,j=B,A < B
i=A,j=B,A<B ,如果希望使得
a
A
>
a
B
a_A>a_B
aA>aB ,那么必须先访问到
B
B
B 再访问
A
A
A 。
有以下三种情况:
对于前两种情况,求子树大小很容易解决。主要考虑第三种情况。
假设起点在绿色区域,首先会有这样一个结论:
证明很容易,因为不论起点在绿色区域哪一点,必然先访问
C
C
C ,再访问
A
A
A 或
B
B
B。
因此考虑起点在绿色区域时先访问到
B
B
B 再访问
A
A
A 的概率,等价于将绿色区域都缩点为
C
C
C 后的概率。
假设 A − B A-B A−B 链上的节点都没有其他分支(比如紫色区域除 F F F 节点以外的其他节点),那么先访问到 B B B 再访问 A A A 的概率可以转化为:
其中 x x x 和 y y y 分别为 C C C 到 B B B 和 A A A 的距离。
这个问题可以用概率DP求解。设
d
p
x
,
y
dp_{x,y}
dpx,y 表示删完第一个堆的概率,则有:
d
p
x
,
y
=
d
p
i
−
1
,
j
+
d
p
i
,
j
−
1
2
dp_{x,y}=\frac{dp_{i-1,j}+dp_{i,j-1}}{2}
dpx,y=2dpi−1,j+dpi,j−1
初始
d
p
0
,
i
=
1
,
d
p
i
,
0
=
0
,
d
p
0
,
0
=
1
(
1
≤
i
≤
n
)
dp_{0,i}=1,dp_{i,0}=0,dp_{0,0}=1(1 \leq i \leq n)
dp0,i=1,dpi,0=0,dp0,0=1(1≤i≤n) 。
但是
A
−
B
A-B
A−B 链上的节点可能有其他分支,例如
F
F
F 节点有紫色区域的分支。
观察后会发现,分支节点对访问到
B
B
B 再访问
A
A
A 的概率无影响,因为其等价于新增一些其他堆,而其他堆不影响第一个堆先被删完还是第二个堆先被删完。
综上, P i , j P_{i,j} Pi,j为:
P i , j = ∑ k ∈ L i n k ( i , j ) d p d i s ( j , k ) , d i s ( i , k ) ∗ s i z e k n P_{i,j}=\sum_{k \in Link(i,j)}{dp_{dis(j,k),dis(i,k)}*\frac{size_k}{n}} Pi,j=k∈Link(i,j)∑dpdis(j,k),dis(i,k)∗nsizek
其中 d i s ( x , y ) dis(x,y) dis(x,y) 表示 x x x 节点与 y y y 节点的距离, s i z e k size_k sizek 表示链 L i n k ( i , j ) Link(i,j) Link(i,j) 上 k k k 节点所在分支的大小。
实现时,可以用 dfs 或 lca 遍历链上节点。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int MAXN=220; const ll mod=1e9+7; int n,siz[MAXN],dsum[MAXN]; ll ans,inv[MAXN],dp[MAXN][MAXN]; vector<int> e[MAXN]; ll powmod(ll x,ll p) { ll ret=1; while(p) { if(p&1) ret=ret*x%mod; x=x*x%mod; p>>=1; } return ret; } void dfs1(int x,int fa) { siz[x]=1; for(int i=0;i<e[x].size();i++) { int son=e[x][i]; if(son==fa) continue; dfs1(son,x); siz[x]+=siz[son]; } } ll frac(ll x,ll y) { return x*inv[y]%mod; } void dfs2(int x,int fa,int d,int root) { if(x<root) { for(int i=0;i<=d-1;i++) ans=(ans+frac(dsum[i],n)*dp[i][d-i]%mod)%mod; } for(int i=0;i<e[x].size();i++) { int son=e[x][i]; if(son==fa) continue; dsum[d]=siz[x]-siz[son]; dfs2(son,x,d+1,root); } } int main() { int i,j,x,y; for(i=1;i<MAXN;i++) inv[i]=powmod(i,mod-2); dp[0][0]=1; for(i=1;i<MAXN;i++) { dp[0][i]=1; dp[i][0]=0; } for(i=1;i<MAXN;i++) for(j=1;j<MAXN;j++) dp[i][j]=(dp[i-1][j]+dp[i][j-1])*inv[2]%mod; scanf("%d",&n); for(i=1;i<n;i++) { scanf("%d%d",&x,&y); e[x].push_back(y); e[y].push_back(x); } ans=0; for(i=1;i<=n;i++) { dfs1(i,-1); dfs2(i,-1,0,i); } printf("%lld\n",ans); }
有一个程序会对长度为 n ( 2 ≤ n ≤ 100 ) n(2 \leq n \leq 100) n(2≤n≤100) 的数组 a a a 和长度为 n − 1 n-1 n−1 的数组 b b b 进行操作。操作次数为无穷多,每次操作具体如下:
最终 a i a_i ai 会分别收敛到某一值。设 F ( a , b ) F(a,b) F(a,b) 为收敛后的 a 1 a_1 a1 。
给定数组
b
b
b ,但不确定数组
a
a
a 。但知道数组
c
c
c ,使得数组
a
a
a 是整数数组且
0
≤
a
i
≤
c
i
(
1
≤
i
≤
n
)
0 \leq a_i \leq c_i(1 \leq i \leq n)
0≤ai≤ci(1≤i≤n)。
求满足条件的
F
(
a
,
b
)
≥
x
F(a,b) \geq x
F(a,b)≥x 的数组
a
a
a 的数量。
每次询问给定一个
x
x
x ,
对于 Easy Version,只有一次询问;
对于 Hard Version,有
q
(
1
≤
q
≤
1
0
5
)
q(1 \leq q \leq 10^5)
q(1≤q≤105) 次询问。
参考 官方 和 syksykCCC 的题解与 sd0061 的代码。
当
a
a
a 收敛时,数组
a
a
a 不再变化,会有
a
i
+
1
−
a
i
=
m
a
x
(
b
i
,
a
i
+
1
−
a
i
)
a_{i+1}-a_i=max(b_i,a_{i+1}-a_i)
ai+1−ai=max(bi,ai+1−ai) 。
也就是说在收敛之前,每次选择
i
i
i 进行操作时,都会同时改变
a
i
a_i
ai 和
a
i
+
1
a_{i+1}
ai+1 ,直到
a
i
+
1
−
a
i
≥
b
i
a_{i+1}-a_i \geq b_i
ai+1−ai≥bi 。
设 f i f_i fi 表示最终收敛的数组。
有以下显然的结论:
设
i
i
i 是第一个不会修改数组的操作,则有:
f
1
+
(
f
1
+
b
1
)
+
(
f
1
+
b
1
+
b
2
)
+
.
.
.
+
(
f
1
+
∑
j
=
1
i
−
1
b
j
)
=
a
1
+
a
2
+
a
3
+
.
.
.
+
a
i
f_1+(f_1+b_1)+(f_1+b_1+b_2)+...+(f_1+\sum_{j=1}^{i-1}{b_j})=a_1+a_2+a_3+...+a_i
f1+(f1+b1)+(f1+b1+b2)+...+(f1+j=1∑i−1bj)=a1+a2+a3+...+ai
设
则上式可以写为:
i
∗
f
1
+
b
p
p
i
−
1
=
a
p
i
i*f_1+bpp_{i-1}=ap_{i}
i∗f1+bppi−1=api
变化可得:
f
1
=
a
p
i
−
b
p
p
i
−
1
i
f_1=\frac{ap_i-bpp_{i-1}}{i}
f1=iapi−bppi−1
我们可以得到 f 1 = m i n i = 1 n ( a p i − b p p i − 1 i ) f_1=min_{i=1}^n(\frac{ap_i-bpp_{i-1}}{i}) f1=mini=1n(iapi−bppi−1) ,证明:
现在问题就变成了存在多少种序列 a a a ,满足 0 ≤ a i ≤ c i 0 \leq a_i \leq c_i 0≤ai≤ci ,使得 m i n i = 1 n ( a p i − b p p i − 1 i ) ≥ x min_{i=1}^n(\frac{ap_i-bpp_{i-1}}{i}) \geq x mini=1n(iapi−bppi−1)≥x 。
设
d
p
i
,
j
dp_{i,j}
dpi,j 表示前
i
i
i 项
a
i
a_i
ai 之和为
j
j
j 的方案数,那么合法条件就变为
j
≥
x
∗
i
+
b
p
p
i
−
1
j\geq x*i+bpp_{i-1}
j≥x∗i+bppi−1 。
状态转移方程为
d
p
i
,
j
=
∑
k
=
m
a
x
(
j
−
c
i
,
0
)
j
d
p
i
−
1
,
k
dp_{i,j}=\sum_{k=max(j-c_i,0)}^{j}{dp_{i-1,k}}
dpi,j=k=max(j−ci,0)∑jdpi−1,k
其中
m
a
x
(
x
∗
i
+
b
p
p
i
−
1
,
0
)
≤
j
≤
c
p
i
max(x*i+bpp_{i-1},0) \leq j \leq cp_i
max(x∗i+bppi−1,0)≤j≤cpi 。
由于转移时求的是区间总和,因此可以用前缀和将每次转移复杂度优化到
O
(
1
)
O(1)
O(1) 。
最终答案为:
a
n
s
=
∑
j
=
0
c
p
n
d
p
n
,
j
ans=\sum_{j=0}^{cp_n}{dp_{n,j}}
ans=j=0∑cpndpn,j
如果每次询问进行计算,可以在 O ( q n 2 m ) O(qn^2m) O(qn2m) 的复杂度内得到答案,其中 m = m a x { c i } m=max\{c_i\} m=max{ci} 。
对于 Easy Version ,
q
=
1
q=1
q=1 复杂度达标。
对于 Hard Version ,发现:
因此只需求解
[
m
i
n
i
=
1
n
(
b
p
p
i
−
1
i
)
,
m
i
n
i
=
1
n
(
b
p
p
i
−
1
i
)
+
m
]
[min_{i=1}^n(\frac{bpp_{i-1}}{i}),min_{i=1}^n(\frac{bpp_{i-1}}{i})+m]
[mini=1n(ibppi−1),mini=1n(ibppi−1)+m] 范围内的答案即可。
采用预处理或者记忆化,将复杂度降为
O
(
n
2
m
2
)
O(n^2m^2)
O(n2m2) 。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int MAXN=110; const int mod=1e9+7; int b[MAXN],c[MAXN],bp[MAXN],bpp[MAXN],cp[MAXN],f[2][2*MAXN*MAXN],ans[MAXN<<1]; inline void add(int &x,int y) { x+=y; if(x>=mod) x-=mod; } int main() { int n,i,j,k; int mn,mx,x,q,nxt,cur; scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&c[i]); cp[i]=cp[i-1]+c[i]; } for(i=1;i<n;i++) { scanf("%d",&b[i]); bp[i]=bp[i-1]+b[i]; bpp[i]=bpp[i-1]+bp[i]; } mn=1<<30; for(i=1;i<=n;i++) mn=min(mn,-bpp[i-1]/i); mx=mn+150; mn=mn-45; memset(ans,-1,sizeof(ans)); scanf("%d",&q); while(q--) { scanf("%d",&x); x=min(mx,max(mn,x)); if(~ans[x-mn]) { printf("%d\n",ans[x-mn]); continue; } cur=1,nxt=0; memset(f,0,sizeof(f)); for(j=0;j<=c[1];j++) f[cur][j]=1; for(i=1;i<=n;i++) { memset(f[nxt],0,sizeof(f[nxt])); for(j=max(x*i+bpp[i-1],0);j<=cp[i];j++) { add(f[nxt][j],f[cur][min(cp[i-1],j)]); if(j-c[i]-1>=0) add(f[nxt][j],mod-f[cur][j-c[i]-1]); } for(j=1;j<=cp[i];j++) add(f[nxt][j],f[nxt][j-1]); swap(cur,nxt); } ans[x-mn]=f[cur][cp[n]]; printf("%d\n",ans[x-mn]); } }
有一个长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \leq n \leq 10^5) n(1≤n≤105) 的排列 p p p ,但你并不知道它,只知道数组 b b b ,其中 b i b_i bi 表示 j < i j < i j<i 且 p j > p i p_j>p_i pj>pi 的 j j j 数量。
有 q ( 1 ≤ q ≤ 1 0 5 ) q(1 \leq q \leq 10^5) q(1≤q≤105) 次询问,询问有两种格式:
参考 froggyzhang 的代码。
对于第
i
i
i 个元素
p
i
p_i
pi ,如果知道共有
q
i
q_i
qi 个元素比其大,那么
p
i
=
n
−
q
i
p_i=n-q_i
pi=n−qi 。
在前
i
−
1
i-1
i−1 个元素中,根据题意可知共有
b
i
b_i
bi 个元素比
p
i
p_i
pi 大。
在之后的
n
−
i
n-i
n−i 个元素中,每增加一个元素
p
x
p_x
px ,有以下两种可能:
这样,就可以在 O ( n ) O(n) O(n) 复杂度内求解出 q i q_i qi 和 p i p_i pi 。
由于存在多次修改和查询,考虑对其分块。
设每块大小为
B
B
B ,
p
i
p_i
pi 所在块的编号为
p
o
s
i
pos_i
posi 。
设
a
i
a_i
ai 表示在
[
1
,
p
o
s
i
−
1
]
[1,pos_i-1]
[1,posi−1] 这些块中,比
p
i
p_i
pi 大的元素个数。
a
i
=
b
i
−
a_i=b_i-
ai=bi− 块内比
p
i
p_i
pi 大的元素个数
可以通过树状数组/线段树,在 O ( B l o g ( n ) ) O(Blog(n)) O(Blog(n)) 的复杂度内求出块内比 p i p_i pi 大的元素个数,维护每一块内所有的 a i a_i ai 。
那么对于任意元素 p x p_x px ,可以分两阶段求解有多少元素大于 p x p_x px :
B = n ⋅ l o g ( n ) B=\sqrt{n \cdot log(n)} B=n⋅log(n) 时最优,总时间复杂度 O ( q n ⋅ l o g ( n ) ) O(q \sqrt{n \cdot log(n)}) O(qn⋅log(n) ) 。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int MAXN=100100; int n; int b[MAXN],l[MAXN],r[MAXN],pos[MAXN],tr[MAXN]; vector<int> v[MAXN]; void add(int x,int d) { while(x<=n) { tr[x]+=d; x+=x&(-x); } } int find(int x) { int ret=0; for(int i=17;i>=0;i--) { if(ret+(1<<i)<n&&tr[ret+(1<<i)]+(1<<i)<x) { ret+=1<<i; x-=tr[ret]+(1<<i); } } return ret+1; } void build(int x) { v[x].clear(); for(int i=l[x];i<=r[x];i++) { int p=find(b[i]+1)-1; v[x].push_back(p); add(p+1,1); } for(int i=0;i<v[x].size();i++) add(v[x][i]+1,-1); sort(v[x].begin(),v[x].end()); } int main() { int i,j,len,blocks,q,op,x,y,ans; scanf("%d",&n); for(i=1;i<=n;i++) scanf("%d",&b[i]); len=min(n,200); blocks=n/len; if(n%len) blocks++; for(i=1;i<=blocks;i++) { l[i]=(i-1)*len+1; r[i]=min(n,i*len); for(j=l[i];j<=r[i];j++) pos[j]=i; } for(i=1;i<=blocks;i++) build(i); scanf("%d",&q); while(q--) { scanf("%d",&op); if(op==1) { scanf("%d%d",&x,&y); b[x]=y; build(pos[x]); } else { scanf("%d",&x); ans=b[x]; for(i=x+1;i<=r[pos[x]];i++) { if(ans>=b[i]) ans++; } for(i=pos[x]+1;i<=blocks;i++) ans+=upper_bound(v[i].begin(),v[i].end(),ans)-v[i].begin(); printf("%d\n",n-ans); } } }
有
n
(
1
≤
n
≤
300
)
n(1 \leq n \leq 300)
n(1≤n≤300) 名厨师,编号从
1
1
1 到
n
n
n 。
厨师
i
i
i 的水平为
i
i
i ,初始能够做出美味度为
a
i
(
∣
a
i
∣
≤
i
)
a_i(|a_i| \leq i)
ai(∣ai∣≤i) 的菜。
每位厨师有一个包含其他厨师的学习列表,可以通过学习列表上的其他厨师来提高自己菜的美味度,但只能水平低的厨师向水平高的厨师学习,反之则不行。
每天厨师们会进行两个阶段的学习,来改变菜的美味度:
每位厨师都会选择使自己菜肴美味度 a i a_i ai 更大的选择。
有 q ( 1 ≤ q ≤ 2 ⋅ 1 0 5 ) q(1 \leq q \leq 2 \cdot 10^5) q(1≤q≤2⋅105) 次询问,每次询问有以下两种格式:
参考 官方 和 p_b_p_b 的题解与 ezoilearner 的代码。
由于厨师总是会采用使得美味度最大的策略,因此
设 d i d_i di 表示最早第 d i d_i di 天后, a i a_i ai 是正数。
注意到题目中有个重要条件,初始
∣
a
i
∣
<
0
|a_i|<0
∣ai∣<0 ,且只能水平低的厨师向水平高的厨师学习。
因此若厨师
j
j
j 在厨师
i
(
j
>
i
)
i(j>i)
i(j>i) 的学习列表中,某一天开始时,
a
i
<
0
,
a
j
>
0
a_i<0,a_j>0
ai<0,aj>0 ,则当天结束后,
a
i
a_i
ai 会变成
a
i
+
j
∗
a
j
≥
−
i
+
j
∗
1
>
0
a_i+j*a_j \geq-i+j*1>0
ai+j∗aj≥−i+j∗1>0 ,即
a
i
a_i
ai 会变成正数。
故可以得到转移式:
d
i
=
m
i
n
j
∈
L
i
s
t
(
i
)
{
d
j
+
1
}
d_i=min_{j \in List(i)}\{d_j+1\}
di=minj∈List(i){dj+1}
给定初始 a i a_i ai ,在 O ( n 2 ) O(n^2) O(n2) 的复杂度内可以求得所有 d i d_i di 。
如果某一天所有
a
i
a_i
ai 都为正数,则从当前天的
{
a
i
}
\{a_i\}
{ai} 变化到下一天
{
a
i
}
\{a_i\}
{ai} 可以用矩阵乘法实现。
具体而言,设
V
k
⃗
\vec{V_k}
Vk
表示第
k
(
x
≥
m
a
x
{
d
i
}
)
k(x \geq max\{d_i\})
k(x≥max{di}) 天每位厨师菜肴美味值
a
i
a_i
ai 构成的
n
n
n 维向量,则
V
k
+
1
⃗
=
A
⋅
V
k
⃗
\vec{V_{k+1}}=A \cdot \vec{V_k}
Vk+1
=A⋅Vk
其中 A A A 为 n ∗ n n*n n∗n 的矩阵,其由以下条件构成:
设
e
i
⃗
\vec{e_i}
ei
表示第
i
i
i 个维度为
1
1
1 的
n
n
n 维单位向量,即
e
i
⃗
=
(
0
,
⋯
,
0
⏟
i
−
1
,
1
,
0
,
⋯
,
0
⏟
n
−
i
)
T
\vec{e_i}=( \underbrace{ 0, \cdots ,0}_{i-1} ,1,\underbrace{0,\cdots ,0}_{n-i} )^\Tau
ei
=(i−1
0,⋯,0,1,n−i
0,⋯,0)T 。
由于当
a
i
a_i
ai 变为正时,会逐渐对厨师
i
i
i 的徒子徒孙产生影响,每过一天多影响一辈的徒弟。
因此利用矩阵
A
A
A ,分别计算每个初始
a
i
a_i
ai 对每个厨师菜肴美味值的贡献,从而求得第
k
k
k 天每名厨师菜肴美味值表示的向量
V
k
⃗
\vec{V_k}
Vk
:
V
k
⃗
=
∑
d
i
≤
k
A
k
−
d
i
e
i
⃗
a
i
+
∑
d
i
>
k
e
i
⃗
a
i
\vec{V_k}=\sum_{d_i \leq k}{A^{k-d_i}\vec{e_i}a_i}+\sum_{d_i>k}{\vec{e_i}a_i}
Vk
=di≤k∑Ak−diei
ai+di>k∑ei
ai
这样就有了一个复杂度为 O ( q n 4 ) O(qn^4) O(qn4) 的解决方案,考虑对其优化。
预习回顾线性代数中关于特征值和特征向量的知识,矩阵的一对特征值
λ
\lambda
λ 和特征向量
v
⃗
\vec{v}
v
存在以下性质:
A
⋅
v
⃗
=
λ
⋅
v
⃗
A\cdot\vec{v}=\lambda\cdot\vec{v}
A⋅v
=λ⋅v
即原先
O
(
n
2
)
O(n^2)
O(n2) 的矩阵乘向量变为了
O
(
n
)
O(n)
O(n) 的常数称向量,考虑以此进行优化。
矩阵
A
A
A 的特征值
λ
\lambda
λ 等于以下方程的解:
d
e
t
(
A
−
λ
I
)
=
0
det(A-\lambda I)=0
det(A−λI)=0
由于题意设定只能水平低的厨师向水平高的厨师学习,因此矩阵 A A A 是一个上三角矩阵,且对角线上恰好是 1 1 1 ~ n n n ,因此:
总计可以在 O ( n 3 ) O(n^3) O(n3) 的复杂度内用高斯消元求解出所有特征向量。
设特征值
λ
i
=
i
\lambda_i=i
λi=i 对应的特征向量为
v
i
⃗
\vec{v_i}
vi
。
利用
A
⋅
v
i
⃗
=
λ
i
⋅
v
i
⃗
A\cdot\vec{v_i}=\lambda_i\cdot\vec{v_i}
A⋅vi
=λi⋅vi
的性质,可以在
O
(
n
⋅
l
o
g
(
k
)
)
O(n \cdot log(k))
O(n⋅log(k)) 的复杂度内求得
A
k
⋅
v
i
⃗
A^k\cdot\vec{v_i}
Ak⋅vi
。如果预处理打表的话,则可以降低到
O
(
n
)
O(n)
O(n) 。
将
e
i
⃗
\vec{e_i}
ei
分解为特征向量
{
v
i
}
\{v_i\}
{vi} 的线性组合,这一操作可以看作求
v
i
{v_i}
vi 构成的矩阵的逆,通过类似高斯消元的方式,可以在
O
(
n
3
)
O(n^3)
O(n3) 复杂度内求出。
设分解结果为
e
i
⃗
=
∑
j
=
1
n
c
i
,
j
v
j
⃗
\vec{e_i}=\sum_{j=1}^n{c_{i,j}\vec{v_j}}
ei
=∑j=1nci,jvj
。那么原先的矩阵乘法,可以优化为:
V
k
⃗
=
∑
d
i
≤
k
A
k
−
d
i
e
i
⃗
a
i
+
∑
d
i
>
k
e
i
⃗
a
i
=
∑
d
i
≤
k
∑
j
=
1
n
λ
j
k
−
d
i
⋅
c
i
,
j
⋅
a
i
⋅
v
j
⃗
+
∑
d
i
>
k
e
i
⃗
a
i
=
∑
j
=
1
n
λ
i
k
⋅
v
j
⃗
⋅
∑
d
i
≤
k
λ
j
−
d
i
⋅
c
i
,
j
⋅
a
i
+
∑
d
i
>
k
e
i
⃗
a
i
a
n
s
(
k
,
l
,
r
)
=
∑
p
=
l
r
V
k
⃗
p
=
∑
j
=
1
n
λ
i
k
⋅
∑
p
=
l
r
v
j
⃗
p
⋅
∑
d
i
≤
k
λ
j
−
d
i
⋅
c
i
,
j
⋅
a
i
+
∑
p
=
l
r
(
d
p
>
k
)
⋅
a
i
其中 ∑ p = l r v j ⃗ p \sum_{p=l}^r{\vec{v_j}_ p} ∑p=lrvj p 可以用前缀和快速求和, ∑ d i ≤ k λ j − d i ⋅ c i , j ⋅ a i \sum_{d_i \leq k}{\lambda_j^{-d_i} \cdot c_{i,j} \cdot a_i} ∑di≤kλj−di⋅ci,j⋅ai 可以用树状数组/线段树进行快速求和,将每次询问的复杂度降为 O ( n log ( n ) ) O(n \log(n)) O(nlog(n)) 。
总时间复杂度为 O ( n 3 + q n log ( n ) ) O(n^3+qn\log(n)) O(n3+qnlog(n)) 。
#include <bits/stdc++.h> typedef long long ll; using namespace std; const int MAXN=330; const int mod=1e9+7; const int inf=1<<30; int pw[MAXN][MAXN+1000]; int inv[MAXN],a[MAXN],h[MAXN],d[MAXN]; int A[MAXN][MAXN],v[MAXN][MAXN],sum[MAXN][MAXN],c[MAXN][MAXN]; int n; int add(int a,int b) { return a+b>=mod?a+b-mod:a+b; } int dec(int a,int b) { return a-b<0?a-b+mod:a-b; } int mul(int a,int b) { return 1ll*a*b%mod; } int MOD(int x) { x%=mod; if(x<0) x+=mod; return x; } void addBIT(int *t,int x,int d) { while(x<=n) { t[x]=add(t[x],d); x+=x&(-x); } } int query(int *t,int x) { int ret=0; while(x) { ret=add(ret,t[x]); x-=x&(-x); } return ret; } void build() { memset(sum,0,sizeof(sum)); int i,j; for(i=n;i>=1;i--) { //求 d[i] if(a[i]>0) d[i]=0; else d[i]=inf; for(j=i+1;j<=n;j++) { if(A[i][j]) d[i]=min(d[i],d[j]+1); } //先单点记录修改 if(d[i]!=inf) { for(j=1;j<=i;j++) { if(c[i][j]) sum[j][d[i]+1]=add(sum[j][d[i]+1], mul(mul(pw[j][n-d[i]],c[i][j]),MOD(a[i]))); } } } //汇总到树状数组上 for(i=1;i<=n;i++) { for(j=1;j<=n;j++) sum[i][j]=add(sum[i][j-1],sum[i][j]); for(j=n;j>=1;j--) sum[i][j]=dec(sum[i][j],sum[i][j^(j&(-j))]); } } int main() { int i,j,k,sz,q,op,l,r,id,x,t,tmp,ans; scanf("%d",&n); inv[0]=inv[1]=1; //O(N)求逆元 for(i=2;i<=n;i++) inv[i]=mul(mod-mod/i,inv[mod%i]); //打指数表 for(i=1;i<=n;i++) { pw[i][n]=1; for(j=n-1;j>=0;j--) pw[i][j]=mul(pw[i][j+1],inv[i]); for(j=n+1;j<=n+1000;j++) pw[i][j]=mul(pw[i][j-1],i); } for(i=1;i<=n;i++) scanf("%d",&a[i]); //构造矩阵 A for(i=1;i<=n;i++) { scanf("%d",&sz); A[i][i]=i; while(sz--) { scanf("%d",&j); A[i][j]=j; } } //求特征向量 for(i=1;i<=n;i++) { v[i][i]=1; for(j=i-1;j>=1;j--) { t=dec(i,j); tmp=0; for(k=j+1;k<=i;k++) tmp=add(tmp,mul(v[i][k],A[j][k])); v[i][j]=mul(tmp,inv[t]); } } //拆分单位向量 for(i=1;i<=n;i++) { h[i]=1; for(j=i;j>=1;j--) { if(h[j]) { c[i][j]=h[j]; t=h[j]; for(k=1;k<=j;k++) h[k]=dec(h[k],mul(t,v[j][k])); } } } //特征向量前缀和处理 for(i=1;i<=n;i++) for(j=1;j<=n;j++) v[i][j]=add(v[i][j-1],v[i][j]); build(); scanf("%d",&q); while(q--) { scanf("%d",&op); if(op==1) { scanf("%d%d%d",&x,&l,&r); ans=0; for(i=1;i<=n;i++) { tmp=query(sum[i],min(x+1,n)); ans=add(ans,mul(mul(tmp,pw[i][x+n]),dec(v[i][r],v[i][l-1]))); } for(i=l;i<=r;i++) { if(d[i]>x) ans=add(ans,MOD(a[i])); } printf("%d\n",ans); } else { scanf("%d%d",&id,&x); if(a[id]<=0&&a[id]+x>0) { a[id]+=x; build(); } else { a[id]+=x; if(d[id]!=inf) { for(i=1;i<=id;i++) { if(c[id][i]) addBIT(sum[i],d[id]+1,mul(mul(c[id][i],x),pw[i][n-d[id]])); } } } } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。