暑期训练队内补题领锅大全

这个暑假注定是忙碌的一夏,渡渡鸟幼儿园采用分锅补题制度力求大幅度提升补题效率,基本上由$yangdavid$撰写训练实录总览,三人分别在各自博客上撰写所补题目的题解

opentrains 1531 300iq contest 1

problem D

题目描述:孙路约炮,$m\leq10^5$个女的,每个女的$l_i,r_i,p_i$表述这个女的在$l-r$区间可约且约了会有$p$的喜悦值,区间值域$[1,n]\leq10^5$,每个女的最多约一次,每天有一个属性$a_i\leq10^9$表示这一天最多约这么多次,求最大总喜悦值,有一个特殊的限制,保证$l_i\leq l_{i+1},r_i\leq r_{i+1}$

场上想出正解,但由于代码复杂度较高加上自己码力不够导致没有$A$掉,硬核线段树题

$solution$:把第$i$天拆成$a_i$天,变成每天最多只能约一次

根据经典贪心模型,可知按照女人的$p$从大到小考虑,尽量把$p$大的女生加入决策

下面我们把女生当成区间

设一个合法的区间集合为,存在一种方式,使得可以在每个区间内选一个点,使得这些点两两不重复

问题就变成了:动态维护一个合法区间集合,支持

$(1)$:询问如果把一个新区间加入当前区间集合,则这个区间集合是否仍然合法

$(2)$:把一个区间加入当前合法区间集合,保证加入后新区间集合仍合法。

我们首先考虑:给定一个区间集合,判定此集合是否合法

由于有$l_i\leq l_{i+1},r_i\leq r_{i+1}$的良好条件,我们把这些区间按$id$排序,则可以从小到大贪心在每个区间里选点,选点要尽量往左选。即可判定。

这就可以引出一个显然而重要的观察:一个合法区间集合中,按照贪心决策的选点方案,各区间所对应点的顺序和区间编号顺序相同

我们用一个支持动态插入的线段树维护当前合法区间集合,线段树基于的下标即区间的编号$[1,m]$,线段树上需要维护的东西叙述如下:

设已插入的$i$号区间所对应的点为$s$,它在集合中编号从小到大为$p$,我们记$r_i-s$为该区间的“后退余地$u$”,$s-p$为该区间的松散系数$v$。

对于每个节点,我们需要维护:

$(1)$.子树中后退余地最小值。

$(2)$.子树中松散系数最大值。

$(3)$.$lazytag1$表示$s$的整体变化。

$(4)$.$lazytag2$表示$p$的整体变化。

显然上述四个值容易维护。下面我们来诠释所需的两种操作如何进行。

1.询问如果把一个新区间加入当前区间集合,则这个区间集合是否仍然合法

设当前加入的区间编号$x$

a.找到当前集合中关于编号$x$的$upper$_$bound\ y$的上一个区间$y_0$,则根据之前描述的贪心方案可知$x$区间对应的点为$s_x=max(s_{y_0},l_x)$

b.在$[y,n]$中找到集合中编号最小的后退余地为$0$的区间$z$,若没有则需使用虚拟节点特殊处理一下。

关于实现,这里找的话,需要用一些比平常有一点点麻烦的技巧(先左后右,找右则全左),保证复杂度一个$log$

c.如果$v_z>s_x-p_{y_0}$,则表明加入集合后该集合仍合法,否则不合法。这个式子的推导较容易

2.把一个区间加入当前合法区间集合,保证加入后新区间集合仍合法:

接上面的步骤:

d.找到$[y,z]$中编号最小的$v>s_x-p_{y_0}$的节点$h$,由于$z$满足条件,因此必然可以找到,然后我们就要将$x$插入了,相当于$[y,n]$的位次$p$整体后移$1$,$[y,h]$的选点位置$s$整体后移$1$,线段树区间修改即可。

冗长的代码:

点击隐藏/显示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
#define int ll
struct girl
{
ll l,r,p,id;
}g[300005];
struct node
{
ll siz,mi,tag,lf,mx,tag2;
}tr[1200005];
ll n,t,ans=0,a[300005],i,pid,pv,pfr,u,rp[300005],nl,nr,ut,upp,cnt=0;
const ll inf=1e18;
bool cmp(girl a,girl b){return a.p>b.p||(a.p==b.p&&a.id>b.id);}
void upd(int p,int v,int q)
{
tr[p].mi-=v;
tr[p].mx-=v;
tr[p].mx-=q;
tr[p].tag+=v;
tr[p].tag2+=q;
}
void pd(int p)
{
int v,u;
if(tr[p].siz==0)return;
if(tr[p].tag!=0||tr[p].tag2!=0)
{
v=tr[p].tag;
u=tr[p].tag2;
tr[p].tag=0;
tr[p].tag2=0;
if(tr[2*p].siz>0)upd(2*p,v,u);
if(tr[2*p+1].siz>0)upd(2*p+1,v,u);
}
}
void fd(int p,int l,int r,int v)
{
int mid;
if(tr[p].siz==0)return;
if(l==r)
{
pfr++;
pid=tr[p].lf;
pv=tr[p].mi;
return;
}
mid=(l+r)/2;
pd(p);
if(v<=mid+1)fd(2*p,l,mid,v);
else
{
if(tr[2*p+1].siz>0&&tr[2*p+1].lf<v)
{
pfr+=tr[2*p].siz;
fd(2*p+1,mid+1,r,v);
}
else fd(2*p,l,mid,v);
}
}
void fd3(int p,int l,int r)
{
int mid;
if(tr[p].siz==0)return;
if(tr[p].mi>0)return;
if(l==r)
{
pid=tr[p].lf;
pfr++;
pv=tr[p].mi;
return;
}
mid=(l+r)/2;
if(tr[2*p].siz==0||tr[2*p].mi>0)
{
pfr+=tr[2*p].siz;
fd3(2*p+1,mid+1,r);
}
else fd3(2*p,l,mid);
}
void fd5(int p,int l,int r,int v)
{
int mid;
if(tr[p].siz==0)return;
if(tr[p].mx<=v)return;
if(l==r)
{
pid=tr[p].lf;
return;
}
mid=(l+r)/2;
if(tr[2*p].siz==0||tr[2*p].mx<=v)fd5(2*p+1,mid+1,r,v);
else fd5(2*p,l,mid,v);
}
void fd2(int p,int l,int r,int v)
{
int mid,pu;
if(tr[p].siz==0)return;
if(tr[p].mi>0)return;
if(l==r)
{
pfr++;
pid=tr[p].lf;
pv=tr[p].mi;
return;
}
mid=(l+r)/2;
pd(p);
if(v>=mid)pfr+=tr[2*p].siz,fd2(2*p+1,mid+1,r,v);
else
{
pu=pfr;
fd2(2*p,l,mid,v);
if(pid!=0)return;
pfr=pu+tr[2*p].siz;
pu=pfr;
fd3(2*p+1,mid+1,r);
if(pid==0)pfr+=tr[2*p+1].siz;
}
}
void fd4(int p,int l,int r,int u,int v)
{
int mid,pu;
//printf("^%lld %lld %lld\n",l,r,tr[p].mx);
if(tr[p].siz==0)return;
if(tr[p].mx<=v)return;
if(l==r)
{
pid=tr[p].lf;
return;
}
mid=(l+r)/2;
pd(p);
if(u>=mid)fd4(2*p+1,mid+1,r,u,v);
else
{
fd4(2*p,l,mid,u,v);
if(pid!=0)return;
fd5(2*p+1,mid+1,r,v);
}
}
void ins(int p,int l,int r,int pt,int v,int vf)
{
int mid;
if(pt<l||pt>r)return;
tr[p].siz++;
tr[p].mi=min(tr[p].mi,v);
tr[p].lf=min(tr[p].lf,pt);
tr[p].mx=max(tr[p].mx,vf);
pd(p);
mid=(l+r)/2;
if(l==r)return;
ins(2*p,l,mid,pt,v,vf);
ins(2*p+1,mid+1,r,pt,v,vf);
}
void modify(int p,int l,int r,int gl,int gr)
{
int mid;
if(gr<l||gl>r)return;
if(tr[p].siz==0)return;
if(l>=gl&&r<=gr)
{
tr[p].mi--;
tr[p].mx++;
tr[p].tag++;
return;
}
pd(p);
mid=(l+r)/2;
modify(2*p,l,mid,gl,gr);
modify(2*p+1,mid+1,r,gl,gr);
tr[p].mi=min(tr[2*p].mi,tr[2*p+1].mi);
tr[p].mx=max(tr[2*p].mx,tr[2*p+1].mx);
}
void modify2(int p,int l,int r,int v)
{
int mid;
if(tr[p].siz==0)return;
if(v>r)return;
if(l==r)
{
tr[p].mx--;
tr[p].tag2++;
return;
}
pd(p);
mid=(l+r)/2;
if(v>mid)modify2(2*p+1,mid+1,r,v);
else
{
if(tr[2*p+1].siz==0)goto tag;
tr[2*p+1].mx--;
tr[2*p+1].tag2++;
tag:modify2(2*p,l,mid,v);
}
tr[p].mi=min(tr[2*p].mi,tr[2*p+1].mi);
tr[p].mx=max(tr[2*p].mx,tr[2*p+1].mx);
}
signed main()
{
scanf("%lld%lld",&n,&t);
for(i=1;i<=t;i++)
{
scanf("%lld",&a[i]);
a[i]+=a[i-1];
}
for(i=1;i<=n;i++)
{
scanf("%lld%lld%lld",&g[i].l,&g[i].r,&g[i].p);
rp[i]=g[i].r;
g[i].id=i;
}
for(i=1;i<=4*n;i++)tr[i].mi=tr[i].lf=inf,tr[i].mx=-inf;
sort(g+1,g+n+1,cmp);
for(i=1;i<=n;i++)
{
pid=pv=pfr=0;
fd(1,1,n,g[i].id);
if(pid!=0)u=a[rp[pid]]-pv+1;
else u=0;
if(pid==0)ut=0;
else ut=pfr;
nl=max(u,a[g[i].l-1]+1);
nr=a[g[i].r];
//printf("#%lld %lld %lld %lld\n",u,pid,pv,nr);
if(nl>nr)continue;
pid=pv=pfr=0;
fd2(1,1,n,g[i].id);
if(pid==0)upp=a[t],pfr=cnt+1,pid=n+1;
else upp=a[rp[pid]]-1;
//printf("%lld %lld %lld %lld %lld\n",upp,nl,pfr,ut,ans);
if(upp-nl+1>pfr-1-ut)
{
cnt++;
ans+=g[i].p;
//printf("%lld\n",ans);
pid=0;
//printf("%lld\n",nl-ut-1);
fd4(1,1,n,nl,nl-ut-1);
if(pid==0)pid=n+1;
ins(1,1,n,g[i].id,a[rp[g[i].id]]-nl,nl-ut-1);
//printf("*%lld %lld\n",g[i].id+1,pid-1);
modify2(1,1,n,g[i].id+1);
if(g[i].id+1<=pid-1&&g[i].id+1<=n)modify(1,1,n,g[i].id+1,pid-1);
}
}
printf("%lld\n",ans);
return 0;
}

Problem E.

给定一张$n$个点的平面图,$n\leq 3000$,随机游走,问从$1$号点期望走几步到达$n$号点

首先,根据欧拉公式:$V+F-2=E$,$F\leq\frac{2E}{3}$,可得$E\leq 3n-6$,按照习惯我们把边数称为$m$,可知平面图中$n,m$同阶。

我们设$n$阶列向量数列${A}$,$A_{ij}$表示走$i$步没走到过$n$第一次走到$j$的概率。我们可以轻易得到一个$n$阶方阵$B$,来递推这个向量数列,$BA_i=A_{i+1}$,$B$中非零元素的数量有$m$个

根据$Cayley-Hamilton$定理,$F(x)=|xI-B|$是$B$的$n$阶多项式,那么$F(B)=0$成立,也就是说

$B^n=\sum_{i=0}^{n-1}a_iB^i$,两边右乘$A_0$,就可以发现${A}$是$n$阶线性齐次递推数列,由于我们需要知道第$i$步第一次到达$n$的概率,因此我们只需要关心每个向量的第$n$项即可,而$A$是递推的也就说明:第$i$步首次到达$n$的概率$p_i$也是一个线性齐次$n$阶递推数列。

根据$Berlekamp-Massy$算法的相关理论,对于$n$阶递推数列,需打表出$2n$项,便可在$O(4n^2)$找到相应最短递推式。

打表,枚举矩阵中非零元素进行转移,需要$O(2nm)$的时间。

设$p$的生成函数为$P$,可知$P’(1)$即为答案所求的期望步数,由于我们有递推式$G$,我们可以列出方程

$P(1-G)=T$,$T$是数列前$n$项与$1-G$项乘产生的无法抵消的贡献,可以暴力$O(n^2)$计算

因此$P=\frac{T}{1-G}$,要求出$P’(1)$我们不能考虑用多项式除法求$P$因为我们要求精确值而不是模$x^n$意义下,直接使用分式求导公式即可

$P’(1)=\frac{T’(1)(1-G(1))+G’(1)T(1)}{(1-G(1))^2}$

点击隐藏/显示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define MAXN 262144
typedef long long ll;
const ll mod=998244353;
ll pow(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
vector<ll> BM(ll n,const vector<ll>&a)
{
ll i=0,j=0,cnt=0,k=0,p=0,dt[6105]={0},fl[6105]={0};
vector<ll> seqp,seq,f;
seqp.clear();seq.clear();f.clear();
const ll mod=998244353;
for(i=1;i<=n;i++)
{
dt[i]=a[i];
for(j=0;j<seq.size();j++)dt[i]=(dt[i]-a[i-j-1]*seq[j]%mod+mod)%mod;
dt[i]=(dt[i]%mod+mod)%mod;
if(dt[i])
{
fl[++cnt]=i;
if(cnt==1){seq.resize(i);p=1;continue;}
f.clear();f.resize(i-fl[p]-1);k=dt[i]*pow(dt[fl[p]],mod-2LL)%mod;
f.push_back(k);for(j=0;j<seqp.size();j++)f.push_back((mod-k)*seqp[j]%mod);
if(i-fl[p]+seqp.size()>=seq.size())
{
p=cnt;
seqp.resize(seq.size());
seqp=seq;
}
if(seq.size()<f.size())seq.resize(f.size());
for(j=0;j<f.size();j++)seq[j]=(seq[j]+f[j])%mod;
}
}
vector<ll> uv;
uv.push_back(0);
for(i=0;i<seq.size();i++)uv.push_back(seq[i]);
return uv;
}
ll n,m,i,j,k,x,y,p[2][3005],invdeg[3005],nw=0,v,up[3005],Len,ans,a,b,a1,b1;
vector<ll>s[3005],fw,A;
int main()
{
//freopen("233.txt","w",stdout);
fw.push_back(0);
scanf("%lld",&n);
for(i=1;i<=n;i++)scanf("%lld%lld",&x,&y);
scanf("%lld",&m);
for(i=1;i<=m;i++)
{
scanf("%lld%lld",&x,&y);
s[x].push_back(y);
s[y].push_back(x);
invdeg[x]++;
invdeg[y]++;
}
for(i=1;i<=n;i++)invdeg[i]=pow(invdeg[i],mod-2LL);
p[nw][1]=1;
for(i=1;i<=2*n+10;i++)
{
nw=!nw;
for(j=1;j<n;j++)
{
p[nw][j]=0;
for(k=0;k<s[j].size();k++)
{
v=s[j][k];
if(v==n)continue;
p[nw][j]=(p[nw][j]+invdeg[v]*p[!nw][v]%mod)%mod;
}
}
p[nw][n]=0;
for(k=0;k<s[n].size();k++)
{
v=s[n][k];
if(v==n)continue;
p[nw][n]=(p[nw][n]+invdeg[v]*p[!nw][v]%mod)%mod;
}
fw.push_back(p[nw][n]);
}
A=BM(2*n+10,fw);
Len=A.size();
//for(i=1;i<Len;i++)printf("%lld ",A[i]);
//printf("\n");
//for(i=1;i<=10;i++)printf("%lld ",fw[i]);
//printf("\n");
//while(Len<n+1)Len++,A.push_back(0);
for(i=1;i<Len;i++)A[i]=(mod-A[i])%mod;A[0]=1;
for(i=0;i<Len;i++)for(j=0;j<Len;j++)up[i+j]=(up[i+j]+A[i]*fw[j])%mod;
a=b=a1=b1=0;
//up[Len-1]=0;
//for(i=0;i<Len;i++)printf("%lld\n",up[i]);
for(i=0;i<Len;i++)
{
a=(a+A[i])%mod;
a1=(a1+A[i]*(i-1))%mod;
b=(b+up[i])%mod;
b1=(b1+up[i]*(i-1))%mod;
}
ans=(a*b1%mod-b*a1%mod+mod)%mod*pow(a*a%mod,mod-2LL)%mod;
printf("%lld\n",ans);
return 0;
}

opentrains 6311 SEERC2018

Problem D

一棵有边权的$n$个点的树,要求从$1$号点出发,经过每条边最少一次,最后回到$1$号点,经过一条边你就要花边权那么多的花费,你最多可以飞跃$m$次,飞跃一次要花费$k$,问最少花多少钱。$n\leq1000$

核心观察:将所有飞跃起始点和飞跃终点作为点不区分地放在树上,则对于一个点$x$,如果$x$的子树里有偶数个点,则这个点的父边经过了$2$次,否则经过了$1$次

问题就变成了在树上放点,最小化花费,经典树形背包问题,树形背包复杂度:

$O(\sum_xsiz[x]^2-\sum_{v\in son[x]}siz[v]^2)=O(n^2)$

注意树形背包时从后往前扫。

想的时候把需要返回$1$号点这个条件不小心忘掉了,导致没过,有点内疚

点击隐藏/显示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
struct edge
{
ll x,c;
}h;
ll ans=0,t,_,n,x,y,v,mx,lim,siz[1005],m,k,i,dp[1005][1005];
vector<edge>s[1005];
void dfs(int p,int f,ll v)
{
ll i,j,k,tmp;
edge h;
siz[p]=1;
dp[p][0]=dp[p][1]=0;
dp[p][1]=v;
for(i=0;i<s[p].size();i++)
{
h=s[p][i];
if(h.x==f)continue;
dfs(h.x,p,h.c);
for(j=siz[p]+1;j<=siz[p]+siz[h.x];j++)dp[p][j]=0;
for(j=siz[p];j>=0;j--)
{
for(k=siz[h.x];k>=0;k--)
{
tmp=dp[p][j]+dp[h.x][k];
if((j+k)%2&&j%2==0)tmp+=v;
if((j+k)%2==0&&j%2)tmp-=v;
dp[p][j+k]=max(dp[p][j+k],tmp);
//printf("%d %lld %lld %lld %lld %lld\n",p,h.x,j,k,dp[p][j+k],tmp);
}
}
siz[p]+=siz[h.x];
}
//printf("%d %lld##\n",p,v);
//for(i=0;i<=siz[p];i++)printf("%lld %lld\n",i,dp[p][i]);
}
int main()
{
scanf("%lld",&t);
for(_=1;_<=t;_++)
{
ans=0;
scanf("%lld%lld%lld",&n,&m,&k);
for(i=1;i<=n;i++)s[i].clear();
for(i=1;i<n;i++)
{
scanf("%lld%lld%lld",&x,&y,&v);
ans+=v;
s[x].push_back((edge){y,v});
s[y].push_back((edge){x,v});
}
dfs(1,0,0);
mx=0;
for(i=0;i<=min(n,2*m+1);i+=2)mx=max(mx,dp[1][i]-k*(i/2));
printf("%lld\n",2LL*ans-mx);
}
return 0;

}

Problem H

这个题意就是一坨屎,其实题目想做这个事(原题意有歧义)

给定一个有向图,构造一种点黑白染色的方案使得至少有$m/4$条从白点到黑点的边。

构造算法:首先把边全都视为无向,每次染色的时候,看看当前点相邻的已经染色的点中是黑的多还是白的多,染成数量少的那一方,显然这样染色完后一定有至少$m/2$的边两边颜色不同。

恢复有向边,按照刚才的方案,从白到黑的边和从黑到白的边至少有一方的数量大于$m/4$,如果是后者,将所有点黑白反转即可。

随机还能过,也是惊了。

体验极差,没代码(都是题意的锅)

waiting:GP of china egh