文章目录
2018.10.24
据说是九校联考的题(虽然不知道哪九校),老师拿来考了,考得还算不错吧…
T1 Backpack
题目大意: 给定
n
(
≤
1
0
6
)
n(\le 10^6)
n(≤106)个物品,每个物品有体积
a
(
≤
100
)
a(\le 100)
a(≤100)和价格
b
(
≤
100
)
b(\le 100)
b(≤100),同种物品可以取无穷多个,问总重量不超过
m
(
≤
1
0
16
)
m(\le 10^{16})
m(≤1016)的情况下能获得的最大价值。
做法: 本题需要用到完全背包+贪心。
乍一看就是裸的完全背包,但由于容量太大,就算对于体积去重,并进行二进制压缩也只能进行到
m
≤
1
0
5
m\le 10^5
m≤105次方的级别。这可怎么办呢?注意到单个物品体积很小,而容量很大,那么这些容量的很大一个部分,一定是取性价比最高的那一个。因此我们贪心地取性价比最高的元素,取到剩下的
m
≤
1
0
5
m\le 10^5
m≤105时再DP)即可)。
(不过出题人貌似有严格证明,有待进一步理解)
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
int n,tot;
ll m,mx[120]={0},f[100010]={0};
ll a[2010],b[2010],ans;
int main()
{
scanf("%d%lld",&n,&m);
ll mxa=1,mxb=0;
for(int i=1;i<=n;i++)
{
int a;ll b;
scanf("%d%lld",&a,&b);
mx[a]=max(mx[a],b);
if (mxa*b>mxb*a)
mxa=a,mxb=b;
}
ll l=0,r=m/mxa;
while(l<r)
{
ll mid=(l+r)>>1;
if ((mid+1ll)*mxa+100000>m) r=mid;
else l=mid+1;
}
m-=l*mxa;
ans=l*mxb;
tot=0;
for(int i=1;i<=100;i++)
if (mx[i])
{
ll nowa=i,nowb=mx[i];
while(nowa<=m)
{
a[++tot]=nowa;
b[tot]=nowb;
nowa<<=1,nowb<<=1;
}
}
for(int i=1;i<=tot;i++)
for(int j=m;j>=a[i];j--)
f[j]=max(f[j],f[j-a[i]]+b[i]);
printf("%lld",f[m]+ans);
return 0;
}
T2 Sort
题目大意: 给定一个长为
2
n
(
n
≤
5
×
1
0
5
)
2n(n\le 5\times 10^5)
2n(n≤5×105)的不递减的序列,要求维护区间加同一个值,并对一个长为偶数的区间询问:定义一个长为偶数的区间的划分为,将这个区间的元素分为两个部分,其中对这个区间每一个前缀,在
A
A
A部分中的元素个数都大于等于在
B
B
B部分中的元素个数,令
A
i
,
B
i
A_i,B_i
Ai,Bi为从左到右第
i
i
i个
A
A
A或
B
B
B部分中的元素,问
∑
(
B
i
−
A
i
)
\sum (B_i-A_i)
∑(Bi−Ai)的最大值和最小值,以及合法划分的方案数。保证序列任何时候都是不递减的。
做法: 本题需要用到线段树+卡特兰数。
方案数显然就是长为
2
k
2k
2k的合法括号序列数量,也就是卡特兰数
1
k
+
1
⋅
C
2
k
k
\frac{1}{k+1}\cdot C_{2k}^k
k+11⋅C2kk。而差值最大,显然应该是选择的
B
i
B_i
Bi之和越大差值越大,而序列不递减,因此
B
B
B选择右边的一半是最优的,线段树维护区间和即可。差值最小的话,一定是选一个
A
A
A,选一个
B
B
B,再选一个
A
A
A…这样的形式,用线段树分别维护区间奇偶位置的元素和即可,时间复杂度为
O
(
n
log
n
)
O(n\log n)
O(nlogn)。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int n,m;
ll fac[1000010],inv[1000010],invfac[1000010];
ll a[1000010],sum[4000010],pk[4000010][2];
ll tag[4000010];
void update(int no,int l,int r,ll d)
{
ll p0=(r-l+2)>>1,p1=(r-l+1)>>1;
tag[no]+=d;
sum[no]+=(ll)(r-l+1)*d;
pk[no][0]+=p0*d;
pk[no][1]+=p1*d;
}
void pushdown(int no,int l,int r)
{
int mid=(l+r)>>1;
if (tag[no]!=0)
{
update(no<<1,l,mid,tag[no]);
update(no<<1|1,mid+1,r,tag[no]);
tag[no]=0;
}
}
void pushup(int no,int l,int r)
{
int mid=(l+r)>>1;
sum[no]=sum[no<<1]+sum[no<<1|1];
pk[no][0]=pk[no<<1][0];
pk[no][1]=pk[no<<1][1];
bool f=(mid+1-l)%2;
pk[no][0]+=pk[no<<1|1][f];
pk[no][1]+=pk[no<<1|1][!f];
}
void buildtree(int no,int l,int r)
{
tag[no]=0;
if (l==r)
{
sum[no]=pk[no][0]=a[l];
pk[no][1]=0;
return;
}
int mid=(l+r)>>1;
buildtree(no<<1,l,mid);
buildtree(no<<1|1,mid+1,r);
pushup(no,l,r);
}
void modify(int no,int l,int r,int s,int t,ll d)
{
if (l>=s&&r<=t)
{
update(no,l,r,d);
return;
}
int mid=(l+r)>>1;
pushdown(no,l,r);
if (s<=mid) modify(no<<1,l,mid,s,t,d);
if (t>mid) modify(no<<1|1,mid+1,r,s,t,d);
pushup(no,l,r);
}
ll querysum(int no,int l,int r,int s,int t)
{
if (l>=s&&r<=t) return sum[no];
int mid=(l+r)>>1;
ll ret=0;
pushdown(no,l,r);
if (s<=mid) ret+=querysum(no<<1,l,mid,s,t);
if (t>mid) ret+=querysum(no<<1|1,mid+1,r,s,t);
return ret;
}
ll querypk(int no,int l,int r,int s,int t)
{
if (l>=s&&r<=t)
{
bool f=(l-s)%2;
return pk[no][!f]-pk[no][f];
}
int mid=(l+r)>>1;
ll ret=0;
pushdown(no,l,r);
if (s<=mid) ret+=querypk(no<<1,l,mid,s,t);
if (t>mid) ret+=querypk(no<<1|1,mid+1,r,s,t);
return ret;
}
int main()
{
scanf("%d%d",&n,&m);
n<<=1;
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
for(ll i=2;i<=n;i++)
{
fac[i]=fac[i-1]*i%mod;
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
invfac[i]=invfac[i-1]*inv[i]%mod;
}
buildtree(1,1,n);
for(int i=1;i<=m;i++)
{
int op,x,y;
ll d;
scanf("%d",&op);
if (op==0)
{
scanf("%d%d%lld",&x,&y,&d);
modify(1,1,n,x,y,d);
}
else
{
scanf("%d%d",&x,&y);
int mid=(x+y)>>1,len=(y-x+1)>>1;
printf("%lld ",(querysum(1,1,n,mid+1,y)-querysum(1,1,n,x,mid))%mod);
printf("%lld ",querypk(1,1,n,x,y)%mod);
printf("%lld\n",fac[len<<1]%mod*invfac[len]%mod*invfac[len+1]%mod);
}
}
return 0;
}
T3 Digit
题目大意: 求位数在
l
(
≤
1
0
18
)
l(\le 10^{18})
l(≤1018)到
r
(
≤
1
0
18
)
r(\le 10^{18})
r(≤1018)位之间的
k
(
≤
1
0
18
)
k(\le 10^{18})
k(≤1018)进制数中,数位和是
5
5
5的倍数,或是
4
4
4的倍数,或是
6
6
6的倍数的数的数量。
做法: 本题需要用到容斥+矩阵快速幂+等比二分。
题目中这个形式显然容斥处理一下,规约成求数位和为某个数
p
p
p的倍数的数有多少个。差分转化一下,变成求位数
≤
x
\le x
≤x的满足要求的数有多少个。令
f
(
i
,
j
)
f(i,j)
f(i,j)为
i
i
i位数中,数位和对
p
p
p取模余数为
j
j
j的数的数量,有状态转移方程:
f
(
i
,
j
)
=
∑
n
x
t
=
0
p
−
1
c
n
t
(
n
x
t
)
⋅
f
(
i
−
1
,
(
j
−
n
x
t
)
%
p
)
f(i,j)=\sum_{nxt=0}^{p-1}cnt(nxt)\cdot f(i-1,(j-nxt)\%p)
f(i,j)=∑nxt=0p−1cnt(nxt)⋅f(i−1,(j−nxt)%p)
其中
c
n
t
(
n
x
t
)
cnt(nxt)
cnt(nxt)指
0
0
0 ~
k
−
1
k-1
k−1中对
p
p
p取模余数为
n
x
t
nxt
nxt的数的数量,这个可以
O
(
p
)
O(p)
O(p)处理出来。看到这个递推式的形式,想到用矩阵快速幂优化DP,但我们最后要求的是
∑
i
=
1
x
f
(
i
,
0
)
\sum_{i=1}^x f(i,0)
∑i=1xf(i,0),所以还要等比二分。弄完了上面一堆操作,我们达到了
O
(
l
c
m
(
5
,
4
,
6
)
3
log
r
)
O(lcm(5,4,6)^3\log r)
O(lcm(5,4,6)3logr)的复杂度。
然而还是会爆,
l
=
r
=
k
=
1
0
18
l=r=k=10^{18}
l=r=k=1018时开O2都得跑8.5s。观察上面写出的转移矩阵,发现这个矩阵是一个循环矩阵(指上一行向右移一位等于下一行的矩阵),我们发现循环矩阵和循环矩阵相加或相乘还是循环矩阵,所以我们只需要维护矩阵的第一行就行了,那么矩阵乘法的时间复杂度就降低为
O
(
l
c
m
(
5
,
4
,
6
)
2
)
O(lcm(5,4,6)^2)
O(lcm(5,4,6)2),于是
O
(
l
c
m
(
5
,
4
,
6
)
2
log
n
)
O(lcm(5,4,6)^2\log n)
O(lcm(5,4,6)2logn)的时间复杂度能轻松地通过此题。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int m,tot;
ll l,r,k,ans,path[1010];
struct matrix
{
ll s[100];
}A[70],E,S,Ans;
ll F[100];
matrix Mult(matrix A,matrix B)
{
memset(S.s,0,sizeof(S.s));
for(int i=0;i<m;i++)
for(int j=0;j<m;j++)
S.s[i]=(S.s[i]+A.s[j]*B.s[(m+i-j)%m])%mod;
return S;
}
matrix Add(matrix A,matrix B)
{
for(int i=0;i<m;i++)
S.s[i]=A.s[i]+B.s[i];
return S;
}
matrix Power(ll b)
{
S=E;
int i=0;
while(b)
{
if (b&1) S=Mult(S,A[i]);
i++;b>>=1;
}
return S;
}
void calc_A()
{
memset(E.s,0,sizeof(E.s));
E.s[0]=1;
for(int i=0;i<m;i++)
{
F[i]=(k/(ll)m)%mod;
if (i<=k%(ll)m) F[i]=(F[i]+1ll)%mod;
}
for(int i=0;i<m;i++)
A[0].s[i]=F[(m-i)%m];
for(int i=1;(1ll<<i)<=r;i++)
A[i]=Mult(A[i-1],A[i-1]);
F[0]=(F[0]-1ll+mod)%mod;
}
matrix calc(ll x)
{
Ans=E;
tot=0;
while(x)
{
path[++tot]=x;
if (x%2ll==0) x>>=1;
else x--;
}
for(int i=tot-1;i>=1;i--)
{
if (path[i]==path[i+1]+1)
Ans=Add(Mult(Ans,A[0]),E);
else Ans=Mult(Ans,Add(Power(path[i]>>1),E));
}
return Ans;
}
void solve(int x,ll type)
{
ll ret=0;
m=x;
calc_A();
calc(r);
for(int i=0;i<m;i++)
ret=(ret+Ans.s[i]*F[i])%mod;
if (l>1)
{
calc(l-1);
for(int i=0;i<m;i++)
ret=((ret-Ans.s[i]*F[i])%mod+mod)%mod;
}
ans=(ans+type*ret)%mod;
}
int main()
{
scanf("%lld%lld%lld",&l,&r,&k);
k--;
ans=0;
solve(5,1ll);
solve(4,1ll);
solve(6,1ll);
solve(20,mod-1ll);
solve(12,mod-1ll);
solve(30,mod-1ll);
solve(60,1ll);
printf("%lld",ans);
return 0;
}
2018.10.25
T1 Median
题目大意: 给定两个长为
n
n
n的非严格单调递增的序列
A
,
B
A,B
A,B,
m
m
m个操作,要求维护单点修改(不改变非严格单调递增的性质),以及询问
A
A
A的区间
[
l
1
,
r
1
]
[l_1,r_1]
[l1,r1]和
B
B
B的区间
[
l
2
,
r
2
]
[l_2,r_2]
[l2,r2]拼起来之后的中位数,保证区间拼起来后长度为奇数。
n
≤
5
×
1
0
5
,
m
≤
1
0
6
n\le 5\times 10^5,m\le 10^6
n≤5×105,m≤106。
做法: 本题需要用到trie/权值线段树+二分。
很快能想到二分套lower_bound之类奇奇怪怪的两个
log
\log
log算法,然而这题数据范围明示了你需要用一个
log
\log
log的算法,这怎么办呢?
我们发现上述算法中最大的问题就是两边的二分不同步,所以要使两边二分同步,只有一个方法:权值线段树/trie,因此我们直接对两个序列分别开权值线段树,两边同时二分即可。
(结果我却没想出来,我好菜啊)
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,tot=0,rt[2]={0},a[2][500010];
int ch[30000010][2]={0},siz[30000010]={0};
int read()
{
char c = getchar(); int x = 0; for(;c < '0' || c > '9'; c = getchar());
for(;c >= '0' && c <= '9'; c = getchar()) x = x * 10 - '0' + c; return x;
}
void insert(int &v,int l,int r,int x,int d)
{
if (!v) v=++tot;
if (l==r) {siz[v]+=d;return;}
int mid=(l+r)>>1;
if (x<=mid) insert(ch[v][0],l,mid,x,d);
else insert(ch[v][1],mid+1,r,x,d);
siz[v]=siz[ch[v][0]]+siz[ch[v][1]];
}
int query(int v1,int v2,int l,int r,int x,int y,int z,int d,int k,int sum1,int sum2)
{
if (l==r) return l;
int mid=(l+r)>>1;
if (max(min(y,sum1+siz[ch[v1][0]])-x+1,0)+max(min(d,sum2+siz[ch[v2][0]])-z+1,0)>=k)
return query(ch[v1][0],ch[v2][0],l,mid,x,y,z,d,k,sum1,sum2);
else return query(ch[v1][1],ch[v2][1],mid+1,r,x,y,z,d,k,sum1+siz[ch[v1][0]],sum2+siz[ch[v2][0]]);
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++)
{
a[0][i]=read();
insert(rt[0],0,1000000000,a[0][i],1);
}
for(int i=1;i<=n;i++)
{
a[1][i]=read();
insert(rt[1],0,1000000000,a[1][i],1);
}
for(int i=1;i<=m;i++)
{
int op,x,y,z,d;
op=read();
if (op==1)
{
x=read(),y=read(),z=read();
insert(rt[x],0,1000000000,a[x][y],-1);
a[x][y]=z;
insert(rt[x],0,1000000000,a[x][y],1);
}
else
{
x=read(),y=read(),z=read(),d=read();
printf("%d\n",query(rt[0],rt[1],0,1000000000,x,y,z,d,(y+d-x-z+3)>>1,0,0));
}
}
return 0;
}
T2 Min
题目大意: 给定一个函数
F
(
x
)
=
A
x
3
+
B
x
2
+
C
x
+
D
F(x)=Ax^3+Bx^2+Cx+D
F(x)=Ax3+Bx2+Cx+D,其中
A
,
B
,
C
,
D
A,B,C,D
A,B,C,D给定,再给定一个长为
n
n
n的序列
a
a
a,定义一个区间
[
l
,
r
]
[l,r]
[l,r]的价值为
f
(
min
i
=
l
r
a
i
)
f(\min_{i=l}^ra_i)
f(mini=lrai),要求把序列分成任意多段,使得这些段的价值之和最大。
n
≤
2
×
1
0
5
n\le 2\times 10^5
n≤2×105。
做法: 本题需要用到DP+单调栈+堆。
首先我们先写出最好想的DP方程:
f
(
i
)
=
max
{
f
(
j
−
1
)
+
F
(
min
k
=
j
i
a
k
)
}
f(i)=\max\{f(j-1)+F(\min_{k=j}^ia_k)\}
f(i)=max{f(j−1)+F(mink=jiak)}
预处理
F
(
min
k
=
j
i
a
k
)
F(\min_{k=j}^ia_k)
F(mink=jiak),可以做到
O
(
n
2
)
O(n^2)
O(n2)。
然而显然过不了。我们发现当
i
i
i固定时,
g
j
=
min
k
=
j
i
a
k
g_j=\min_{k=j}^ia_k
gj=mink=jiak是一个非严格单调递减的序列,于是可以根据
g
j
g_j
gj分成一段一段,每一段中
g
j
g_j
gj相等,于是我们只需要知道这一段中最大的
f
(
j
−
1
)
f(j-1)
f(j−1),就能得到这些点的最大转移了。而在
i
i
i右移时,显然就是维护一个单调栈了,于是用堆来存储每一段中转移的最大值,转移的时候取堆顶即可(好像也可以直接在单调栈上维护?),因为单调栈中一个元素最多入队一次,出队一次,因此在堆中插入/删除元素最多只有
2
n
2n
2n次,时间复杂度为
O
(
n
log
n
)
O(n\log n)
O(nlogn)。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const ll inf=10000000000001ll;
int n,top;
ll a[200010],f[200010],A,B,C,D;
ll st[200010],mxf[200010];
struct Heap
{
priority_queue<ll> a,b;
void insert(ll x) {a.push(x);}
void Delete(ll x) {b.push(x);}
ll top()
{
while(!b.empty()&&b.top()==a.top())
a.pop(),b.pop();
return a.top();
}
}ans;
ll F(ll x)
{
return A*x*x*x+B*x*x+C*x+D;
}
int main()
{
scanf("%d%lld%lld%lld%lld",&n,&A,&B,&C,&D);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
f[0]=0;top=0;
for(int i=1;i<=n;i++)
{
ll nowmx=f[i-1],nowst=a[i];
while(top&&nowst<=st[top])
{
nowmx=max(nowmx,mxf[top]);
ans.Delete(mxf[top]+F(st[top]));
top--;
}
st[++top]=nowst,mxf[top]=nowmx;
ans.insert(mxf[top]+F(st[top]));
f[i]=ans.top();
}
printf("%lld",f[n]);
return 0;
}
T3 Max
题目大意: 有
n
n
n个排成一排的魔法阵,有
m
m
m块魔法石,第
i
i
i块魔法石有
p
i
p_i
pi的概率,以
y
i
y_i
yi的价值在第
x
i
x_i
xi个魔法阵上出现。魔法阵的价值是在它上面出现的所有魔法石中价值的最小值。要汲取
q
q
q次能量,每次汲取一个区间
[
l
,
r
]
[l,r]
[l,r],保证这些区间互不包含,汲取的结果是第
l
l
l到第
r
r
r个魔法阵价值的最大值,求
q
q
q次汲取能得到的结果总和的期望值。
q
≤
n
≤
1
0
5
,
m
≤
2
×
1
0
5
q\le n\le 10^5,m\le 2\times 10^5
q≤n≤105,m≤2×105。
做法: 本题需要用到概率期望+线段树。
挺神的一道题。根据期望可加性,显然可以分开考虑每个询问的期望值再相加,令
a
n
s
ans
ans为询问的结果,根据整数概率公式有:
E
[
a
n
s
]
=
∑
x
=
1
∞
P
(
a
n
s
≥
x
)
E[ans]=\sum_{x=1}^\infty P(ans\ge x)
E[ans]=∑x=1∞P(ans≥x)
=
∑
x
=
1
∞
1
−
P
(
a
n
s
<
x
)
=\sum_{x=1}^\infty 1-P(ans<x)
=∑x=1∞1−P(ans<x)
而
a
n
s
<
x
ans<x
ans<x的概率,显然等于区间中每一个魔法阵价值都
<
x
<x
<x的概率,令
v
i
v_i
vi为第
i
i
i个魔法阵的价值,则有:
E
[
a
n
s
]
=
∑
x
=
1
∞
(
1
−
∏
i
=
l
r
P
(
v
i
<
x
)
)
E[ans]=\sum_{x=1}^\infty (1-\prod_{i=l}^rP(v_i<x))
E[ans]=∑x=1∞(1−∏i=lrP(vi<x))
=
∑
x
=
1
∞
(
1
−
∏
i
=
l
r
[
1
−
P
(
v
i
≥
x
)
]
)
=\sum_{x=1}^\infty (1-\prod_{i=l}^r[1-P(v_i\ge x)])
=∑x=1∞(1−∏i=lr[1−P(vi≥x)])
注意到
x
>
max
i
=
l
r
v
i
x>\max_{i=l}^r v_i
x>maxi=lrvi的时候,里面的式子为
0
0
0,这就说明期望是有限的,因此我们只需计算
x
≤
max
i
=
l
r
v
i
x\le\max_{i=l}^r v_i
x≤maxi=lrvi的情况即可.当然上限弄大一点也不会错,也方便一些后面的处理,因此我们框一个上界
n
≥
max
i
=
l
r
v
i
n\ge \max_{i=l}^rv_i
n≥maxi=lrvi,发现那个
1
−
.
.
.
1-...
1−...的东西可以拆开,变成:
E
[
a
n
s
]
=
n
−
∑
x
=
1
n
∏
i
=
l
r
[
1
−
P
(
v
i
≥
x
)
]
E[ans]=n-\sum_{x=1}^n\prod_{i=l}^r[1-P(v_i\ge x)]
E[ans]=n−∑x=1n∏i=lr[1−P(vi≥x)]
于是我们只需要维护目前的
∏
i
=
l
r
[
1
−
P
(
v
i
≥
x
)
]
\prod_{i=l}^r[1-P(v_i\ge x)]
∏i=lr[1−P(vi≥x)]即可,令这个东西为
Q
Q
Q。
先考虑
P
(
v
i
≥
x
)
P(v_i\ge x)
P(vi≥x)怎么求。
v
i
v_i
vi要
≥
x
\ge x
≥x,就意味着在第
i
i
i个魔法阵上,价值
<
x
<x
<x的全不出现,并且价值
≥
x
\ge x
≥x的至少有一个出现。这也就是价值
<
x
<x
<x的全不出现的概率,减去这个魔法阵上所有的魔法石都不出现的概率(因为条件可以看成,
<
x
<x
<x全不出现且存在魔法石出现)。这些东西我们可以
O
(
m
log
m
)
O(m\log m)
O(mlogm)排序后
O
(
m
)
O(m)
O(m)预处理。
进一步地,考虑从大到小枚举
x
x
x,我们发现当且仅当
x
x
x达到某一个可能存在的价值时,对应的
P
(
v
i
≥
x
)
P(v_i\ge x)
P(vi≥x)才会变化,于是我们把可能存在的价值从大到小排序,在处理
x
x
x改变到一个价值
v
a
l
val
val后的
Q
Q
Q之后,下一个会改变到的价值是
n
x
t
nxt
nxt,你可以直接在答案里累加
(
v
a
l
−
n
x
t
)
×
Q
(val-nxt)\times Q
(val−nxt)×Q。当然,这个
n
x
t
nxt
nxt是可以等于
v
a
l
val
val的,我们应该在把所有的影响处理完后再加上对应的贡献,具体的话可以自己看代码理解一下。
我们发现,
Q
Q
Q的改变总是这样的一种形式:
Q
−
>
Q
×
1
−
P
(
v
i
≥
n
x
t
)
1
−
P
(
v
i
≥
v
a
l
)
Q->Q\times \frac{1-P(v_i\ge nxt)}{1-P(v_i\ge val)}
Q−>Q×1−P(vi≥val)1−P(vi≥nxt)
看上去只要求个逆元就好了,实际上有个问题:
1
−
P
(
v
i
≥
v
a
l
)
=
0
1-P(v_i\ge val)=0
1−P(vi≥val)=0怎么办?注意到
n
x
t
≤
v
a
l
nxt\le val
nxt≤val,而
P
(
v
i
≥
x
)
P(v_i\ge x)
P(vi≥x)随
x
x
x减少而增大,所以当
1
−
P
(
v
i
≥
v
a
l
)
=
0
1-P(v_i\ge val)=0
1−P(vi≥val)=0时同样有
1
−
P
(
v
i
≥
n
x
t
)
=
0
1-P(v_i\ge nxt)=0
1−P(vi≥nxt)=0,于是我们不用管它。
这样我们就得到了一个
O
(
m
q
)
O(mq)
O(mq)的算法。然而并过不了,我们发现还有一个区间互不包含的条件没用,于是我们把这些区间按左端点排序,那么一个魔法阵会影响到的区间编号是一个连续的区间。我们又发现最后只需要求所有区间
a
n
s
ans
ans的和即可,于是我们可以用线段树同时维护所有区间的
Q
Q
Q,维护区间乘和整体求和即可,时间复杂度为
O
(
m
log
q
)
O(m\log q)
O(mlogq)。
我傻逼的地方:注意某些点可能不会影响任何区间,我们直接不讨论这些点的影响,免得做线段树的时候RE…
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int n,m,q,tot,nowp[200010],l[200010]={0},r[200010]={0};
ll tag[400010],seg[400010];
ll old[200010],alldown[200010],partdown[200010],lastp[200010],lastdown[200010];
struct point
{
int x;
ll y,p;
}p[200010];
struct interval
{
int l,r;
}t[200010];
bool cmp(point a,point b) {return a.y<b.y;}
bool cmpt(interval a,interval b) {return a.l<b.l;}
void update(int no,ll d)
{
tag[no]=tag[no]*d%mod;
seg[no]=seg[no]*d%mod;
}
void pushdown(int no)
{
if (tag[no]!=1)
{
update(no<<1,tag[no]);
update(no<<1|1,tag[no]);
tag[no]=1;
}
}
void pushup(int no)
{
seg[no]=(seg[no<<1]+seg[no<<1|1])%mod;
}
void buildtree(int no,int l,int r)
{
tag[no]=1;
if (l==r) {seg[no]=1;return;}
int mid=(l+r)>>1;
buildtree(no<<1,l,mid);
buildtree(no<<1|1,mid+1,r);
pushup(no);
}
void modify(int no,int l,int r,int s,int t,ll d)
{
if (l>=s&&r<=t)
{
update(no,d);
return;
}
int mid=(l+r)>>1;
pushdown(no);
if (s<=mid) modify(no<<1,l,mid,s,t,d);
if (t>mid) modify(no<<1|1,mid+1,r,s,t,d);
pushup(no);
}
ll power(ll a,ll b)
{
ll s=1,ss=a;
while(b)
{
if (b&1) s=s*ss%mod;
ss=ss*ss%mod;b>>=1;
}
return s;
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;i++)
scanf("%d%lld%lld",&p[i].x,&p[i].y,&p[i].p);
for(int i=1;i<=q;i++)
scanf("%d%d",&t[i].l,&t[i].r);
sort(p+1,p+m+1,cmp);
for(int i=1;i<=max(n,m);i++)
alldown[i]=partdown[i]=1;
for(int i=1;i<=m;i++)
{
if (lastp[p[i].x])
partdown[i]=partdown[lastp[p[i].x]]*(mod+1ll-p[lastp[p[i].x]].p)%mod;
else partdown[i]=1ll;
lastp[p[i].x]=i;
alldown[p[i].x]=alldown[p[i].x]*(mod+1ll-p[i].p)%mod;
}
p[0].y=0;
sort(t+1,t+q+1,cmpt);
int L=1,R=0;
for(int i=1;i<=q;i++)
{
while(R<t[i].r) l[++R]=i;
while(L<t[i].l) r[L++]=i-1;
}
while(L<=R) r[L++]=q;
buildtree(1,1,q);
for(int i=1;i<=n;i++)
old[i]=1;
ll ans=0;
for(int i=m;i>=1;i--)
{
ll newp=((1ll-partdown[i]+alldown[p[i].x])%mod+mod)%mod;
if (l[p[i].x])
{
modify(1,1,q,l[p[i].x],r[p[i].x],newp*power(old[p[i].x],mod-2)%mod);
old[p[i].x]=newp;
}
ans=(ans+(p[i].y-p[i-1].y)*seg[1])%mod;
}
ans=(((ll)q*p[m].y-ans)%mod+mod)%mod;
printf("%lld",ans);
return 0;
}
2018.10.30
这次比赛我没考,所以代码就不写了,只讲讲思路。
T1 Slope
题目大意: 给定二维平面上的
n
n
n个点,再给定一个斜率
P
/
Q
P/Q
P/Q,求出一组点使得它们之间连线的斜率和
P
/
Q
P/Q
P/Q的差的绝对值最小。
n
≤
2
×
1
0
5
n\le 2\times 10^5
n≤2×105。
做法: 本题需要用到计算几何。
直接
O
(
n
2
)
O(n^2)
O(n2)暴力肯定会挂。但我们发现,假设我们找到了这一对点,我们过这两个点各作一条斜率为
P
/
Q
P/Q
P/Q的直线,则这两条直线之间应该不包含任何点,否则这就不是最优解。于是我们过每个点都作一条这样的直线,按作出的直线的截距排序,最优解只可能存在于这个顺序上相邻两个点之间,于是我们就以
O
(
n
log
n
)
O(n\log n)
O(nlogn)的时间复杂度解决了这一题。
T2 Tree
题目大意: 有
n
n
n个点,每个点有键值
k
i
k_i
ki和权值
v
i
v_i
vi,要求用这些点构造出一棵关于键值的二叉查找树,使得树上任意相邻两点的键值的
gcd
\gcd
gcd不为
1
1
1,并令
s
u
m
i
sum_i
sumi为以
i
i
i为根子树内所有点
v
i
v_i
vi的和,求合法的二叉查找树中
∑
i
=
1
n
s
u
m
i
\sum_{i=1}^n sum_i
∑i=1nsumi的最大值。
n
≤
300
,
k
i
≠
k
j
(
i
≠
j
)
n\le 300,k_i\ne k_j(i\ne j)
n≤300,ki=kj(i=j)。
做法: 本题需要用到区间DP。
首先,一棵树是二叉查找树的充要条件是,树的中序遍历和点按键值从小到大排序的排列是一样的。因此我们在这个排列上DP,令
f
(
l
,
r
,
i
)
f(l,r,i)
f(l,r,i)为区间
[
l
,
r
]
[l,r]
[l,r]内的点构成一棵子树,并且根编号为
i
i
i时,子树内所有点
s
u
m
sum
sum之和的最大值,那么有状态转移方程:
f
(
l
,
r
,
i
)
=
max
{
f
(
l
,
i
−
1
,
p
)
+
f
(
i
+
1
,
r
,
q
)
∣
gcd
(
k
p
,
k
i
)
≠
1
,
gcd
(
k
q
,
k
i
)
≠
1
}
f(l,r,i)=\max\{f(l,i-1,p)+f(i+1,r,q)|\gcd(k_p,k_i)\ne 1,\gcd(k_q,k_i)\ne 1\}
f(l,r,i)=max{f(l,i−1,p)+f(i+1,r,q)∣gcd(kp,ki)=1,gcd(kq,ki)=1}
看上去复杂度为
O
(
n
5
)
O(n^5)
O(n5),但我们发现
p
,
q
p,q
p,q之间互不影响,所以我们分别枚举即可,时间复杂度为
O
(
n
4
)
O(n^4)
O(n4)。但这样还是过不了,我们又发现对于一个区间
[
l
,
r
]
[l,r]
[l,r],它对一个更大的区间有贡献,当且仅当这个更大的区间形成的子树以
l
−
1
l-1
l−1或
r
+
1
r+1
r+1为根。因此我们在处理一个区间时,枚举根
i
i
i,向下递归计算,并同时计算出对于
l
−
1
,
r
+
1
l-1,r+1
l−1,r+1两个点的贡献最大值即可,时间复杂度为
O
(
n
3
)
O(n^3)
O(n3)。
T3 Forging
题目大意: 给定一棵
n
n
n个点的有根树,每个点有点权,边有边权,
q
q
q次询问,每次询问一个点
x
x
x和一个数
k
k
k,问点
x
x
x的子树中和点
x
x
x的距离
≥
k
\ge k
≥k的所有点的权值和。
1
≤
n
,
q
≤
1
0
5
1\le n,q\le 10^5
1≤n,q≤105。
做法: 本题需要用到主席树。
这个题可以转化成,在DFS序上的一个区间内,求深度
≥
d
e
p
(
x
)
+
k
\ge dep(x)+k
≥dep(x)+k的所有点的权值和。因此我们对深度离散化,询问的时候二分算出实际上的询问区间,然后我们以DFS序为时刻,以深度为下标建立主席树即可做到
O
(
n
log
n
)
O(n\log n)
O(nlogn)。
2018.11.3
这一次比赛我还是没有参加,也是只写一下思路。
T1 Flower
题目大意: 等比数列求前缀和,模数任意。
做法: 本题需要用到等比二分。
因为模数任意,直接套公式会有算不出来逆元的尴尬情况,所以等比二分即可。
T2 Mole
大模拟题,就不写了。
T3 Seer
题目大意: 给定一个
n
n
n个点,
m
m
m条边的带边权的有向图,令一个环路的权值为,上面所有边权的几何平均数:
∏
i
=
1
n
a
i
n
\sqrt[n]{\prod_{i=1}^n a_i}
n∏i=1nai,现在从点
i
i
i出发走一个回路,令
v
i
v_i
vi为这条回路上可能包含的环路的最大权值,求所有
v
i
v_i
vi。
n
,
m
≤
5000
n,m\le 5000
n,m≤5000。
做法: 本题需要用到SCC缩点+01分数规划+SPFA判负环。
首先把所有数都换成对数,众所周知,对数有以下优美的性质:
log
(
a
⋅
b
)
=
log
a
+
log
b
\log (a\cdot b)=\log a+\log b
log(a⋅b)=loga+logb
log
(
a
n
)
=
1
n
⋅
log
a
\log(\sqrt[n]a)=\frac{1}{n}\cdot \log a
log(na)=n1⋅loga
那么环的权值就可以表示成
1
n
⋅
∑
i
=
1
n
a
i
\frac{1}{n}\cdot \sum_{i=1}^na_i
n1⋅∑i=1nai的形式了。要求出这个的最大值,显然01分数规划+SPFA判负环即可。
但是题目好像不是单纯让我们求最大环,我们还要考虑每个点能走到的最大环。熟悉图论的同学应该很清楚了,直接SCC缩点,对每个SCC进行01分数规划即可。
2018.11.4
这场比赛我又是没参加,还是只写思路,耶!
T1 Boom
题目大意: 想象炸弹人这个游戏,现在有一个
n
×
m
n\times m
n×m的网格(
n
×
m
≤
300
n\times m\le 300
n×m≤300),每个网格有一盏灯,初始的状态可能是开或关,一个炸弹会影响一个点以及它上下左右四个点的灯的状态,会将它们取反,问将所有灯关上至少需要多少个炸弹。
做法: 本题需要用到枚举。
显然,第一行要炸哪些点确定了,所有格子中哪些格子要炸就确定了,所以枚举第一行(当然要选较小的一维作为行),然后再
O
(
n
m
)
O(nm)
O(nm)算出下面哪些点要炸就行了,时间复杂度为
O
(
2
min
(
n
,
m
)
n
m
)
O(2^{\min(n,m)}nm)
O(2min(n,m)nm),因为
n
m
≤
300
nm\le 300
nm≤300,所以
min
(
n
,
m
)
≤
18
\min(n,m)\le 18
min(n,m)≤18,可以通过此题。
T2 Farm
题目大意: 求比一个给定的排列
A
A
A字典序大的,恰好有
k
k
k个逆序对的长为
n
n
n的排列数目。
n
,
k
≤
5000
n,k\le 5000
n,k≤5000。
做法: 本题需要用到DP。
这题神似NOI2018Day1T2,思路也类似。先不管字典序的限制,考虑求长为
n
n
n的排列中,逆序对数恰好为
k
k
k的排列数目。
考虑我们平时求逆序对数的方法。一是从左往右插入数字,每次累加在它左边的比它大的数字的数目。但我们发现用这种做法难以转移,因此我们考虑另一种方法:将所有数字排序后从小到大插入,每次累加已经插入的在它右边的数字数目。令
f
(
n
,
k
)
f(n,k)
f(n,k)为长为
n
n
n的排列中,逆序对数恰好为
k
k
k的排列数目,那么往里面新插入
n
+
1
n+1
n+1后,新增的逆序对数可能是
0
0
0~
n
n
n中任何一个,因此有了状态转移方程:
f
(
n
,
k
)
=
∑
i
=
0
n
−
1
f
(
n
−
1
,
k
−
i
)
f(n,k)=\sum_{i=0}^{n-1}f(n-1,k-i)
f(n,k)=∑i=0n−1f(n−1,k−i)
可以用前缀和优化到
O
(
n
k
)
O(nk)
O(nk)。
于是我们开始考虑字典序的限制。考虑枚举从左到右第一个大于给定排列
A
A
A的字符位置
i
i
i,那么显然在字典序的限制下,在这个位置上放一个比
A
i
A_i
Ai大的数,然后这个位置后面的位置就可以随便选了。假设在这个位置上放的数是
x
x
x,那么整个排列的逆序对数,可以拆分成:
1
1
1 ~
i
−
1
i-1
i−1内部的逆序对数;一端在
1
1
1 ~
i
−
1
i-1
i−1,另一端在
i
i
i ~
n
n
n的逆序对数;一端是
i
i
i,另一端在
i
+
1
i+1
i+1 ~
n
n
n的逆序对数;
i
+
1
i+1
i+1 ~
n
n
n内部的逆序对数。前面三个部分都可以用一些简单的预处理以
O
(
n
k
)
O(nk)
O(nk)的总时间复杂度得到,那么后面部分的逆序对数应该为
k
−
k-
k−上面那些部分的和
s
s
s,而后面部分是随便选的,相当于一个排列,因此累加
f
(
n
−
i
−
1
,
k
−
s
)
f(n-i-1,k-s)
f(n−i−1,k−s)入答案即可。这样我们就
O
(
n
k
)
O(nk)
O(nk)地解决了这一题。
T3 Pet
题目大意: 有
n
n
n个点,每个点有一个坐标,现在有三种操作:将第
l
l
l到
r
r
r个点按照某一向量移动;将所有点的坐标还原为某个时刻上的状态;询问第
l
l
l到第
r
r
r个点中两两距离的最大值。这里的距离指,从点
(
x
,
y
)
(x,y)
(x,y)一次可以向八个方向走:
(
0
,
1
)
,
(
1
,
0
)
,
(
1
,
1
)
,
(
1
,
−
1
)
,
.
.
.
(0,1),(1,0),(1,1),(1,-1),...
(0,1),(1,0),(1,1),(1,−1),...(把这里列出的四个全部取反就是另外四个),从一个点走到另一个点的最小步数。
做法: 本题需要用到主席树。
显然这里的距离就等于
max
(
∣
x
i
−
x
j
∣
,
∣
y
i
−
y
j
∣
)
\max(|x_i-x_j|,|y_i-y_j|)
max(∣xi−xj∣,∣yi−yj∣)。于是对每一个区间维护区间
x
,
y
x,y
x,y的最大、最小值,用主席树+标记永久化计算即可。
2018.11.5
T1 Fib
题目大意: 给定一个数
A
(
≤
1
0
9
)
A(\le 10^9)
A(≤109),判断它能不能被拆分成两个斐波那契数的乘积。
做法: 斐波那契数列增长很快,直接暴力枚举即可。
T2 Equal
题目大意: 给定一棵树,多次询问到两个点
a
a
a和
b
b
b的距离相等的点有多少个。
做法: 经典倍增+LCA水题,不讲了。
T3 Tree
题目大意: 给定一棵树,问至少留下多少条边,使得存在至少
K
K
K个点,满足它们至少都和这
K
K
K个点中的另一个连通。
K
≤
N
≤
1
0
5
K\le N\le 10^5
K≤N≤105。
做法: 本题需要用到树形DP。
令保留的边数为
e
e
e,把思路转换一下,不考虑对一个
K
K
K最小的
e
e
e是多少,转而考虑对一个
e
e
e最大的
K
K
K是多少。
显然,一个点只要被保留的边覆盖,它就是合法点,那么为了使合法点数量最多,能同时连接两个不合法点就连接。那么问题来了,最多可以连接多少条这样的边呢?这显然是个最大匹配问题(因为树是二分图,所以我直接写最大匹配了),令最大匹配数为
x
x
x,那么当
e
≤
x
e\le x
e≤x时,最大的
K
K
K为
2
e
2e
2e。而当
e
>
x
e>x
e>x时,每次只能连接一个不合法点了,这时最大的
K
K
K就是
2
e
+
(
x
−
e
)
=
x
+
e
2e+(x-e)=x+e
2e+(x−e)=x+e。那么反过来得到,如果
K
≤
2
x
K\le 2x
K≤2x,最小的
e
e
e为
⌊
K
+
1
2
⌋
\lfloor\frac{K+1}{2}\rfloor
⌊2K+1⌋;如果
K
>
2
x
K>2x
K>2x,最小的
e
e
e为
x
+
(
K
−
2
x
)
=
K
−
x
x+(K-2x)=K-x
x+(K−2x)=K−x。
至于求最大匹配数
x
x
x,可以使用树形DP:令
f
(
i
,
0
/
1
)
f(i,0/1)
f(i,0/1)为以
i
i
i为根的子树中,点
i
i
i没被/被匹配上的情况下的最大匹配数,那么有状态转移方程:
f
(
i
,
0
)
=
∑
s
o
n
max
(
f
(
s
o
n
,
0
)
,
f
(
s
o
n
,
1
)
)
f(i,0)=\sum_{son}\max(f(son,0),f(son,1))
f(i,0)=∑sonmax(f(son,0),f(son,1))
f
(
i
,
1
)
=
max
{
f
(
i
,
0
)
−
max
(
f
(
s
o
n
,
0
)
,
f
(
s
o
n
,
1
)
)
+
f
(
s
o
n
,
0
)
+
1
}
f(i,1)=\max\{f(i,0)-\max(f(son,0),f(son,1))+f(son,0)+1\}
f(i,1)=max{f(i,0)−max(f(son,0),f(son,1))+f(son,0)+1}
这样就可以
O
(
n
)
O(n)
O(n)解决这一题了。当然,因为最大匹配等于最小点覆盖,而一个点覆盖的补集是独立集,这样就可以直接转化成我们熟悉的树上最大独立集来做,也是
O
(
n
)
O(n)
O(n)。