还就那个坑队友,我还是太丢脸了。

因为近期有各种事情,终于在一周后补完了(除了那道概率论题)。

[1001 Miserable Faith]

solution

题面:一棵以 为根的树,开始每个点颜色不同。定义一条边边权为 当且仅当端点颜色不同,否则为 。有四种操作:

  1. 的路径上的所有点都染成一个没出现过的颜色。
  2. 询问 的边权和。
  3. 询问 ​ 的子树内所有点到 的边权和的和。
  4. 定义 ​​ 的最浅相同颜色的祖先到 的距离为 ,求

做法:考场一眼就发现是 NOI 2021 D1T1 的改题了,第一个操作是 LCT 的 access ,第二个操作和第三个操作就是询问虚边。那我就维护实边,虚边就是总边长减重边了。第四个操作放在 LCT 上就是同求每一个点到它所在 splay 的最浅深度点的距离的和。冷静分析一下,由于第一种操作只会到 ,那我连 makeroot 都不用,直接 access ,然后在这过程中有个线段树维护子树边权和就好了。第四个操作只要你对 splay 的每个点的深度取个 ,那这个就能拆成所有深度和减最小深度乘 splay 的点数,​ 只有在虚实边切换的时候会改变,那么都可以在 access 的时候顺手维护好。于是总复杂度就是

具体细节可见代码。

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
//2021.8.3 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
const int N=1e5+10;
namespace MAIN{
int n;
vector<int> G[N];
int fa[N],dep[N],sz[N],son[N],pos[N];
int top[N],dfn[N],low[N],idx;
LL RET;
inline int get_lca(res x,res y){
while(top[x]^top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
return x;
}
namespace T{
LL sum[N<<2];
int laz[N<<2],len[N<<2];
inline void change(const res &rt,const res &val){
sum[rt]+=1ll*val*len[rt],laz[rt]+=val;
}
inline void pushdown(const res &rt){
if(!laz[rt])return;
change(rt<<1,laz[rt]),change(rt<<1|1,laz[rt]),laz[rt]=0;
}
inline void pushup(const res &rt){
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(res rt,res l,res r){
len[rt]=r-l+1,sum[rt]=0,laz[rt]=0;
if(l==r)return;
res mid=(l+r)>>1;
build(rt<<1,l,mid),build(rt<<1|1,mid+1,r);
}
void modify(res rt,res l,res r,const res &L,const res &R,const res &val){
if(L<=l&&r<=R){change(rt,val);return;}
pushdown(rt);
res mid=(l+r)>>1;
if(L<=mid)modify(rt<<1,l,mid,L,R,val);
if(R>mid)modify(rt<<1|1,mid+1,r,L,R,val);
pushup(rt);
}
LL query(res rt,res l,res r,const res &L,const res &R){
if(L<=l&&r<=R)return sum[rt];
pushdown(rt);
res mid=(l+r)>>1;
RG LL ret=0;
if(L<=mid)ret=query(rt<<1,l,mid,L,R);
if(R>mid)ret+=query(rt<<1|1,mid+1,r,L,R);
pushup(rt);
return ret;
}
}
namespace LCT{
struct node{
int ch[2],fa,pre,suf,mndep,sz;
}v[N];
inline bool isroot(const res &x){
return v[v[x].fa].ch[0]!=x&&v[v[x].fa].ch[1]!=x;
}
inline void push_up(const res &rt){
res ls=v[rt].ch[0],rs=v[rt].ch[1];
v[rt].sz=v[ls].sz+v[rs].sz+1;
v[rt].pre=ls?v[ls].pre:rt;
v[rt].suf=rs?v[rs].suf:rt;
v[rt].mndep=min({dep[rt],v[ls].mndep,v[rs].mndep});
}
inline void init(){
for(res i=1;i<=n;i++)v[i].ch[0]=v[i].ch[1]=v[i].fa=v[i].pre=v[i].suf=v[i].mndep=0,v[i].sz=1,push_up(i);
v[0].mndep=n+1;
}
inline void rot(const res &x){
res p=v[x].fa,g=v[p].fa;
RG bool d=v[p].ch[1]==x;
if(!isroot(p))v[g].ch[v[g].ch[1]==p]=x;
v[p].ch[d]=v[x].ch[d^1];
v[v[x].ch[d^1]].fa=p;
v[x].ch[d^1]=p;
v[x].fa=g,v[p].fa=x;
push_up(p),push_up(x);
}
inline void splay(const res &x){
while(!isroot(x)){
res p=v[x].fa,g=v[p].fa;
if(!isroot(p))rot(v[g].ch[0]==p^v[p].ch[0]==x?x:p);
rot(x);
}
}
inline void ins(const res &y){
if(!y)return;
T::modify(1,1,n,dfn[v[y].pre],low[v[y].pre],-1);
}
inline void del(const res &y){
if(!y)return;
T::modify(1,1,n,dfn[v[y].pre],low[v[y].pre],1);
}
inline void split(const res &x){
splay(1);
if(!v[x].ch[0])return;
res y=v[v[x].ch[0]].suf;
splay(y);
if(v[y].ch[1])RET+=1ll*v[v[y].ch[1]].mndep*v[v[y].ch[1]].sz;
v[y].ch[1]=0,push_up(y);
T::modify(1,1,n,dfn[x],low[x],-1);
}
inline int limit_access(res x,const res &Dep){
res y;
for(y=0;x;y=x,x=v[x].fa){
splay(x);
RET-=1ll*v[x].mndep*v[x].sz;
del(y),ins(v[x].ch[1]);
if(v[x].ch[1])RET+=1ll*v[v[x].ch[1]].mndep*v[v[x].ch[1]].sz;
v[x].ch[1]=y,push_up(x);
if(dep[v[x].pre]==Dep)return x;
}
return y;
}
inline void expose(res x){
split(1);
res rt=limit_access(x,dep[1]);
RET+=1ll*v[rt].mndep*v[rt].sz;
}
inline LL ask(const res &x,const res &y){
res lca=get_lca(x,y);
return T::query(1,1,n,dfn[x],dfn[x])+T::query(1,1,n,dfn[y],dfn[y])-2*T::query(1,1,n,dfn[lca],dfn[lca]);
}
}
LL a[N];
void dfs(res x,res fax){
fa[x]=fax,dep[x]=dep[fax]+1,sz[x]=1,son[x]=0,a[x]=0;
for(auto tox:G[x]){
if(tox==fax)continue;
dfs(tox,x),sz[x]+=sz[tox];
if(sz[tox]>sz[son[x]])son[x]=tox;
a[x]+=a[tox];
}
a[x]+=dep[x];
LCT::v[x].fa=fax,LCT::v[x].mndep=dep[x];
}
void dfs_top(res x,res topx){
top[x]=topx,dfn[x]=++idx;
if(son[x])dfs_top(son[x],topx);
for(auto tox:G[x]){
if(tox==fa[x]||tox==son[x])continue;
dfs_top(tox,tox);
}
low[x]=idx;
}
inline void MAIN(){
res T=read();
while(T--){
n=read();
for(res i=1;i<=n;i++)vector<int>().swap(G[i]);
res q=read();
for(res i=1;i<n;i++){
res u=read(),v=read();
G[u].pb(v),G[v].pb(u);
}
LCT::init(),idx=0,dfs(1,0),dfs_top(1,1),T::build(1,1,n);
RG LL RR=0;
RET=0;
for(res i=1;i<=n;i++)RET+=dep[i];
RR=RET;
while(q--){
res opt=read();
if(opt==1){
res u=read();
read();
LCT::expose(u);
}
if(opt==2){
res u=read(),v=read();
printf("%lld\n",dep[u]+dep[v]-2*dep[get_lca(u,v)]-LCT::ask(u,v));
}
if(opt==3){
res u=read();
printf("%lld\n",a[u]-1ll*sz[u]*dep[u]-T::query(1,1,n,dfn[u],low[u])+1ll*sz[u]*T::query(1,1,n,dfn[u],dfn[u]));
}
if(opt==4){
printf("%lld\n",RR-RET);
}
}
}
}
}

[1002 String Mod]

solution

题面:由前 ​ 个小写字母构成长度为 ​ 的字符串,给定 ​​,对每一个 ​,求字符串数量满足 “a” 的数量模 ​ 等于 ​ 且 “b” 的数量模 ​ 等于 ​,答案模 ​,满足

做法:容易对 ​ 写出答案式子,为

看到取模等于一个定值,容易想到单位根反演,于是进行一顿推导(不妨令

于是预处理完单位根就可以 递推出答案了。

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
//2021.8.8 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
const int N=500+10;
namespace MAIN{
int wn[N],iwn[N*N];
int f[N][N],g[N][N];
inline void MAIN(){
res Case=read();
while(Case--){
res k=read()-2,L=int(Read()%(kcz-1)),n=read();
res w=qpow(G,(kcz-1)/n),iw=qpow(w);
wn[0]=iwn[0]=1;
for(res i=1;i<n;i++)wn[i]=mul(wn[i-1],w),iwn[i]=mul(iwn[i-1],iw);
for(res i=n,sz=(n-1)*(n-1);i<=sz;i++)iwn[i]=iwn[i-n];
for(res i=0;i<n;i++)for(res j=0;j<n;j++)f[i][j]=qpow(k+wn[i]+wn[j],L);
for(res i=0;i<n;i++)for(res b=0;b<n;b++){g[i][b]=0;for(res j=0;j<n;j++)add(g[i][b],mul(f[i][j],iwn[b*j]));}
res INV=mul(qpow(n),qpow(n));
for(res a=0;a<n;a++){
for(res b=0;b<n-1;b++){
res ret=0;
for(res i=0;i<n;i++)add(ret,mul(g[i][b],iwn[i*a]));
printf("%d ",mul(ret,INV));
}
res ret=0;
for(res i=0;i<n;i++)add(ret,mul(g[i][n-1],iwn[i*a]));
printf("%d\n",mul(ret,INV));
}
}
}
}

[1003 VC Is All You Need]

solution

题面:是否存在 ​ 维空间中的 ​ 个点和任意的黑白染色方案,使得都存在 ​ 维的超平面可以恰好把相同颜色的点分在同一边。

做法:看到样例,猜测一下就是 ​​。

证明的话我们先考虑二维,那随便选三个不共线的点肯定存在一条线,而选取四个点的话,你对角同色的情况就找不到一条线了。

然后归纳证明,第 ​​ 维的情况就考虑将其投影到一个 ​​ 维空间里同时满足有两个点投影重合,这样就可以证明了。大致就是这样了。

1
2
3
4
5
6
7
8
9
10
11
12
13
//2021.8.8 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
namespace MAIN{
inline void MAIN(){
res Case=read();
while(Case--){
RG LL n=Read(),k=Read();
puts(n<=k+1?"Yes":"No");
}
}
}

[1004 Another String]

solution

题面:定义两个相同长度的字符串的距离为相同位置不同字符的个数。现给出一个字符串 和自然数 ,希望你对每一个 ​​,求出 两者有多少个子串对满足距离小于等于

做法:不知道为什么他标算写得那么麻烦。

表示 ​,这里 定义为距离小于等于 ​​ 且最长的前缀。我们考虑 对答案的贡献,设其为 ​,注意到它能影响的分割点只有 ,而分割点的前一段因为长度小于 ,那么得到的贡献就是它自身到 的长度。后一段则都是 。注意到 可以拆成 ,那就可以维护两个差分数组,分别维护常数贡献和自身位置贡献(即记录自身位置用过几次),于是现在只要能求 就行了。

原本想用后缀结构搞一搞的,冷静一下发现数据范围允许 了,那就维护个单调的指针,从后往前扫一遍就能求出 了。总复杂度

不知道能不能做到 的级别。

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
//2021.8.8 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
const int N=3e3+10;
namespace MAIN{
int n,k;
char str[N];
LL g[N],h[N];
inline void MAIN(){
res Case=read();
while(Case--){
n=read(),k=read(),scanf("%s",str+1);
for(res i=1;i<n;i++)g[i]=h[i]=0;
for(res d=1;d<n;d++){
res r=0,j=n-d;
for(res i=n-d;i;i--){
if(str[i]!=str[i+d])r++;
if(r>k){
while(str[j]==str[j+d]&&j>i)j--;
j--,r--;
}
if(j<i);
else {
res x=min(j-i+1,d);
g[x+i-1]+=x,g[i+d]-=x;
if(x>1)h[i]++,h[x+i-1]--,g[i]+=1-i,g[x+i-1]-=1-i;
}
}
}
for(res i=2;i<n;i++)g[i]+=g[i-1],h[i]+=h[i-1];
for(res i=1;i<n;i++)printf("%lld\n",g[i]+h[i]*i);
}
}
}

[1005 Random Walk 2]

solution

题面:给出 ,从节点 走到 的概率为 ,若从 走到了 就会停止。希望你对每一个 求出,从 出发,到 ​ 停止的​​概率。

做法:设 表示从 出发到 停止的概率,容易写出转移式

发现是个线性方程组,但一算高斯消元的复杂度已经到了 ,放弃。

冷静一下,发现 ​​​ 总是类似 ​​​​ 的物品,于是设 ​​ 构成的矩阵为 ​​,​​ 构成的矩阵为 ​​,但 ​​ 去掉对角线,设 ​​ 的矩阵的对角线矩阵为 ​​,容易发现 ​​,即 ​​,于是写个求逆就好了。

时间复杂度

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
//2021.8.8 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
const int N=300+10;
namespace MAIN{
int n;
int w[N][N];
struct Matrix{
int a[N][N];
inline int* operator [](const res &x){return a[x];}
inline void ide(){
for(res i=1;i<=n;i++)a[i][i]=1;
}
inline void re(){
for(res i=1;i<=n;i++)for(res j=1;j<=n;j++)a[i][j]=read()%kcz;
}
inline void init(){
for(res i=1;i<=n;i++)for(res j=1;j<=n;j++)a[i][j]=0;
}
inline void sw(const res &i,const res &j){
for(res o=1;o<=n;o++)swap(a[i][o],a[j][o]);
}
inline void ast(const res &i,const res &v){
for(res k=1;k<=n;k++)a[i][k]=mul(a[i][k],v);
}
inline void dec(const res &i,const res &j,const res &v,const res &fl){
if(!fl)for(res k=1;k<=n;k++)add(a[i][k],mul(kcz-v,a[j][k]));
else for(res k=j;k<=n;k++)add(a[i][k],mul(kcz-v,a[j][k]));
}
inline void print(){
for(res i=1;i<=n;i++){for(res j=1;j<=n;j++)printf("%d ",a[i][j]);puts("");}
}
}A,B,C;
inline Matrix operator * (const RG Matrix &x,const RG Matrix &y){
RG Matrix o;
for(res i=1;i<=n;i++)for(res j=1;j<=n;j++){
RG LL ret=0;
for(res k=1;k<=n;k++)ret+=1ll*x.a[i][k]*y.a[k][j];
o[i][j]=int(ret%kcz);
}
return o;
}
int d[N];
inline void MAIN(){
res Case=read();
while(Case--){
n=read();
for(res i=1;i<=n;i++){
d[i]=0;
for(res j=1;j<=n;j++)w[i][j]=read(),d[i]+=w[i][j];
d[i]=qpow(d[i]);
for(res j=1;j<=n;j++)w[i][j]=mul(w[i][j],d[i]);
}
for(res i=1;i<=n;i++)for(res j=1;j<=n;j++){
if(i==j)A[i][j]=1,C[i][j]=w[i][j];
else A[i][j]=kcz-w[i][j],C[i][j]=0;
}
B.init(),B.ide();
for(res i=1;i<=n;i++){
res k=i;
for(res j=i+1;j<=n;j++)if(A[j][i]>A[k][i])k=j;
if(i!=k)A.sw(i,k),B.sw(i,k);
if(A[i][i]==0){puts("No Solution");return;}
res inv=qpow(A[i][i]);
for(res j=1;j<=n;j++){
if(i!=j){
res t=mul(A[j][i],inv);
A.dec(j,i,t,1),B.dec(j,i,t,0);
}
}
A.ast(i,inv),B.ast(i,inv);
}
A=B*C;
for(res i=1;i<=n;i++){
res ret=0;
for(res j=1;j<n;j++)printf("%d ",A[i][j]);
printf("%d\n",A[i][n]);
}
}
}
}

[1006 Cute Tree]

solution

题面:计算长度为 的三叉树的节点个数。

做法:模拟。

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
//2021.8.3 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
const int N=1e6+10;
namespace MAIN{
int n;
int son[N][3],tot;
void build(res &rt,res L,res R){
tot++,rt=tot;
if(L==R){return;}
if(L+1==R){
res mid=(L+R)>>1;
build(son[rt][0],L,L),build(son[rt][1],R,R);
}
else {
res B=L+(R-L+2)/3-1,C=(B+R)/2;
build(son[rt][0],L,B),build(son[rt][1],B+1,C),build(son[rt][2],C+1,R);
}
}
int RT;
inline void MAIN(){
res T=read();
while(T--){
n=read(),tot=RT=0;
for(res i=1;i<=n;i++)read();
build(RT,1,n);
printf("%d\n",tot);
}
}
}

[1007 Banzhuan]

solution

题面:用 的小正方体填 ​ 的大正方体,满足重力,问填出三视图均为 的正方形的立体图形最多最少要多少钱?已知将一个正方形放在 耗费的钱是 ,注意可以放在一个地方然后让它掉下去。

做法:只要知道这个注意就做出来了,出题人的题面写得有点锅。

最大就是放在顶上往下掉就行了,最小感受一下就是底面铺满,然后斜对角线摆满就行了。

答案手推一下化个简就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//2021.8.8 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
namespace MAIN{
inline void MAIN(){
res Case=read();
while(Case--){
res n=int(Read()%kcz);
res x=mul(n,mul(qpow(n+1,2),qpow(2)));
res a=mul(mul(x,qpow(6)),mul(n,n<<1|1));
res b=mul(mul(mul(n+2,n-1),qpow(2)),(mul(mul(n+2,n-1),qpow(2))+mul(mul(mul(n,n+1),n<<1|1),qpow(6))-1));
printf("%d\n",Add(a,b));
printf("%d\n",mul(mul(qpow(n,4),mul(qpow(n+1,2),n<<1|1)),qpow(12)));
}
}
}

[1008 Supermarket]

solution

题面:给你 ​​​ 个 ​​ 串,等概率地选择其中任意个,然后或起来。设 ​​ 表示或起来的串包含了 ​​ 的情况下,它包含 ​​ 的概率。求 ​​​。

做法:注意到 ​ 是条件概率,设取出包含 ​ 的概率是 ​​,那么 ​,套上 ​ 就有

注意到 ​​ 和 ​​ 都是在做 ​​ 的过程,而后者则多了 ​​ 的乘法因子(因为当 ​​ 非 ​​ 的位确定时,​​ 含 ​​ 的这几位可以随意选取),于是做两次高维前缀和即可。

时间复杂度

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
//2021.8.9 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
const int N=1<<20;
namespace MAIN{
int n,m;
int b[N+10],d[N+10];
inline void FWT_and(res *A,const res &lim){
for(res mid=1;mid<lim;mid<<=1)for(res i=0;i<lim;i++)if(!(i&mid))add(A[i],A[i^mid]);
}
int pw[N];
inline void MAIN(){
pw[0]=1;
for(res i=1;i<=N-10;i++)pw[i]=Add(pw[i-1],pw[i-1]);
res Case=read();
while(Case--){
n=read(),m=read();
res sz=1<<n;
for(res i=0;i<sz;i++)b[i]=0;
for(res i=1;i<=m;i++)b[read()]++;
FWT_and(b,sz);
for(res i=0;i<sz;i++)d[i]=b[i];
FWT_and(d,sz);
res ret=0;
for(res i=0;i<sz;i++)add(ret,mul(mul(d[i],pw[pc(i)]),qpow(b[i])));
printf("%d\n",ret);
}
}
}

[1009 Array]

solution

题面:给出 ​,问有多少个区间满足区间内出现次数最多的数的个数大于其余数出现个数之和。

做法:不知道为什么我的做法这么复杂。

根据那个我忘记名字的投票法,我们枚举 ,将不是 的数看成 ,其余看成 ,附带着 ​ 号位做一遍前缀和,那每个点为终点的合法区间个数就是看它前面有几个数比它小。我们发现暴力维护这个的复杂度是 ​ 是颜色个数。然后冷静一下,我们对每个 的位置寻求它和它连着的 的贡献。若我们维护一个以前缀和大小为下标的数组 ,那 贡献就是个前缀和,而由于 ​ 处的前缀和是连续的,那就是 分别为 处前缀和的下上界。这个东西显然可以分成两段,一部分是 ,这里还是维护 的前缀和即可。另外一段的系数是等差数列,那我们就可以维护 ​ 的前缀和。修改就是一个区间加,这些都是线段树可以做到的。时间复杂度

另一思路不是维护 ,而是直接维护小于等于 的前缀和个数 。那查询就非常容易了,就是个区间和。修改操作的话, 处是简单的,就是区间加。而 ​ 处则一样可以分成两段,一段是区间加,另一段是加一个等差数列,这照样是维护 ​ 的前缀和可以解决的,还是线段树可以做到。时间复杂度仍然是

出题人的 不太理解,大概是维护差分数组来延迟更新,从而不需要用线段树?不太懂哎,有没有懂哥教教我啊。

当然线段树都可以换成树状数组,线段树有点难以跑过去似乎。

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
//2021.8.9 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
const int N=1e6;
const int M=(N+10)<<3;
namespace MAIN{
int n;
vector<int> A[N+5];
Pair aqua[N<<1];
int ax;
int laz[M],sum[M],len[M];
LL Len[M],Sum[M];
void build(res rt,res l,res r){
laz[rt]=sum[rt]=0,len[rt]=r-l+1,Sum[rt]=0;
if(l==r){Len[rt]=l;return;}
res mid=(l+r)>>1;
build(rt<<1,l,mid),build(rt<<1|1,mid+1,r);
Len[rt]=Len[rt<<1]+Len[rt<<1|1];
}
inline void change(const res &rt,const res &v){
sum[rt]+=v*len[rt],laz[rt]+=v,Sum[rt]+=Len[rt]*v;
}
inline void pushdown(const res &rt){
if(!laz[rt])return;
change(rt<<1,laz[rt]),change(rt<<1|1,laz[rt]),laz[rt]=0;
}
inline void pushup(const res &rt){
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];
}
void modify(res rt,res l,res r,const res &L,const res &R){
if(L<=l&&r<=R){change(rt,1);return;}
pushdown(rt);
res mid=(l+r)>>1;
if(L<=mid)modify(rt<<1,l,mid,L,R);
if(R>mid)modify(rt<<1|1,mid+1,r,L,R);
pushup(rt);
}
void dec(res rt,res l,res r,const res &L,const res &R){
if(L<=l&&r<=R){change(rt,-1);return;}
pushdown(rt);
res mid=(l+r)>>1;
if(L<=mid)modify(rt<<1,l,mid,L,R);
if(R>mid)modify(rt<<1|1,mid+1,r,L,R);
pushup(rt);
}
LL Query(res rt,res l,res r,const res &L,const res &R){
if(L<=l&&r<=R)return Sum[rt];
pushdown(rt);
res mid=(l+r)>>1;
RG LL ret=0;
if(L<=mid)ret=Query(rt<<1,l,mid,L,R);
if(R>mid)ret+=Query(rt<<1|1,mid+1,r,L,R);
pushup(rt);
return ret;
}
int query(res rt,res l,res r,const res &L,const res &R){
if(L<=l&&r<=R)return sum[rt];
pushdown(rt);
res mid=(l+r)>>1,ret=0;
if(L<=mid)ret=query(rt<<1,l,mid,L,R);
if(R>mid)ret+=query(rt<<1|1,mid+1,r,L,R);
pushup(rt);
return ret;
}
inline void MAIN(){
res Case=read();
while(Case--){
n=read(),build(1,-N,N);
for(res i=0;i<=N-10;i++)vector<int>().swap(A[i]),A[i].pb(0);
for(res i=1;i<=n;i++)A[read()].pb(i);
for(res i=0;i<=N-10;i++)A[i].pb(n+1);
RG LL ans=0;
for(res i=0;i<=N-10;i++){
res sz=int(A[i].size()-1);
if(A[i].size()==2)continue;
res now=0;
modify(1,-N,N,0,0);
aqua[ax=1]=mp(0,0);
for(res j=1;j<=sz;j++){
res L=now-(A[i][j]-A[i][j-1]-1),R=now-1;
if(L<=R)ans+=1ll*R*query(1,-N,N,L,R)-Query(1,-N,N,L,R)+1ll*(R-L+1)*query(1,-N,N,-N,L-1);
if(j==sz)continue;
if(L<=R)modify(1,-N,N,L,R),aqua[++ax]=mp(L,R);
now=L+1;
ans+=query(1,-N,N,-N,now-1);
modify(1,-N,N,now,now),aqua[++ax]=mp(now,now);
}
for(res j=1;j<=ax;j++)dec(1,-N,N,aqua[j].fi,aqua[j].se);
}
printf("%lld\n",ans);
}
}
}

[1010 Guess Or Not 2]

好一个概率密度函数,暂时不想补。等我学会概率论就回来补了。

solution

题面:

做法:

1

[1011 Jsljgame]

solution

题面:有 堆石子,每堆有 个石子,先手的人不能取 个石子,后手不能取 个,不能取者输,问谁赢。

做法:特意去重温了超现实数,结果对这道题还是没有好的想法。

然后就只能大力猜结论再证了。

首先 ​ 是容易的,这可以直接转换为平等博弈,证明 ​ 是容易的,归纳还是直接证都是可行的。

就麻烦许多了。我们想用超现实数解,却发现大部分博弈都是 ​ 的情况,难以用超现实数表示。

于是就开始猜结论了。

如果所有数都小于 ,那就是普通的 ​ 游戏,全部异或即可。

否则 ​​。(不妨给 排了序)

接下来我们可以归纳似的猜结论。

先考虑 。​

​​​​,​对于先手人而言,我们肯定想取 ​​ 一部分使得异或和为 ​​ 且 ​​,这样就是先手必胜了。此时要求 ​​ 且 ​​。否则必然会想办法不让后手人使总异或和为 ​​​​。

我们假设先手取之后是 ​​​。我们换位思考后手人的操作再来取决先手人的操作。

,则 ​ 就是先手人胜利的充要条件。

​​,后手人肯定想使异或和为 ,那要求 ,则必然存在 ,希望找到这样的 ,使得 。若找到的 只能使 ,那此时 只能为 (因为其它 )。于是后手恶心人,取 个石子,那先手只能取 ​ 个石子才能使异或和为 ​,可惜它取不了。先手人无法破解当前局面,因为后手人如果不能取 个,那就一直重复取 ​​ 个的操作,先手人必败。

​​。后手人肯定想要先手人只能取 ​​​ 个。注意到异或和为 ​​,这说明 ​​ 的最高位的 ​​ 在 ​​ 中出现了偶数次,同时也表明 ​​ 也有这样一个 ​​(因为 ​​)。那么 ​ 的解只有这些包含最高次 ​ 的数中才会出现。于是我们后手人每次找一个 ​,将其变成 ​。此时异或和的最高次 ​ 是存在的,先手人想要逆转局势,肯定要异或和重新变为 ​,那么动的还是包含最高次 ​ 的 ​。可惜,​ 不包含 ​ 的情况下是奇数个,而我后手人是先动手的。那么我后手人只要一直重复上述操作,你先手人总会轮到必须把 ​ 变成 ​​​,那么你先手人必输无疑。所以这种情况还是先手人必输。

同理地,经过讨论,我们发现在 ​​ 的情况下,只有 的情况下先手才能赢。

就等价于上述讨论了,于是先手必胜。

综上,​ 时是个 ​ 的平等博弈模型。

​ 且 ​ 是 ​ 博弈模型。

则看 是否成立。

​​​ 的其他情况都是后手胜​。

博弈模型。

​ 且 都是先手必胜。

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
//2021.8.13 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
const int N=1e3+10;
namespace MAIN{
int n,x,y;
int a[N];
inline void MAIN(){
res Case=read();
while(Case--){
n=read(),x=read(),y=read();
for(res i=1;i<=n;i++)a[i]=read();
sort(a+1,a+n+1);
res s=0;
if(x==y)for(res i=1;i<=n;i++)s^=a[i]%x+a[i]/(2*x)*x;
else if(x<y){
if(a[n]<x){
for(res i=1;i<=n;i++)s^=a[i];
}
else {
for(res i=1;i<=n;i++)s+=a[i]>=x;
if(s>=2)s=0;
else {
s=0;
for(res i=1;i<n;i++)s^=a[i];
if(a[n]-s>a[n]-x&&a[n]-s!=x)s=1;
else s=0;
}
}
}
else {
if(a[n]<y){
for(res i=1;i<=n;i++)s^=a[i];
}
else s=1;
}
puts(s?"Jslj":"yygqPenguin");
}
}
}

[1012 Yet Another Matrix Problem]

solution

题面:给出 ,问对每个 ,有多少个矩阵对 ,满足 的矩阵, 的矩阵,

做法:不得不说多项式板子间亦有差距!

先进行一波平静的推式子。

​​ 的方案数为 ​​,隔板法可知 ​​。

再设 的方案数为 ,那 卷积,那么 也就 ​ 求出了。 需特殊处理,直接考虑组合意义即可。

直接求 不太好求,把它放在指数上,即设 ,于是题目变成对每一个 ,求

这个多项式快速幂就行了,时间复杂度

坑:多项式取对数一般人的板子都是要求 的,所以要先整体乘上逆元,求完之后再乘回去,注意乘回去的 应该

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
//2021.8.13 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
const int N=4e5+10;
namespace MAIN{
int n,m,r;
namespace poly{
int pos[N],inv[N];
inline void NTT(res *A,const res &type,const res &lim){
for(res i=0;i<lim;i++)if(i<pos[i])swap(A[i],A[pos[i]]);
for(res mid=1;mid<lim;mid<<=1){
res wn=qpow(type==1?G:GI,(kcz-1)/(mid<<1));
for(res j=0;j<lim;j+=mid<<1){
res w=1;
for(res k=0;k<mid;k++,w=1LL*w*wn%kcz){
res x=A[j+k],y=1LL*w*A[j+mid+k]%kcz;
A[j+k]=Add(x,y),A[j+mid+k]=Add(x,kcz-y);
}
}
}
if(type==-1){
res INV=qpow(lim,kcz-2);
for(res i=0;i<=lim;i++)A[i]=1LL*A[i]*INV%kcz;
}
}
inline void derivation(const res *A,res *B,const res &lim){
for(res i=1;i<lim;i++)B[i-1]=1LL*i*A[i]%kcz;
B[lim-1]=0;
}
inline void integral(const res *A,res *B,const res &lim){
for(res i=1;i<lim;i++)B[i]=1LL*A[i-1]*inv[i]%kcz;
B[0]=0;
}
int A[N],B[N],D[N],E[N];
void getinv(res *a,res *b,res lim){
if(lim==1){b[0]=qpow(a[0],kcz-2);return;}
getinv(a,b,lim>>1);
for(res i=0;i<lim;i++)A[i]=a[i],B[i]=b[i];
res qaq=0,len=1;
while(len<=lim)len<<=1,qaq++;
for(res i=0;i<len;i++)pos[i]=(pos[i>>1]>>1)|((i&1)<<(qaq-1));
NTT(A,1,len),NTT(B,1,len);
for(res i=0;i<len;i++)A[i]=1LL*A[i]*B[i]%kcz*B[i]%kcz;
NTT(A,-1,len);
for(res i=0;i<lim;i++)b[i]=Add(b[i],Add(b[i],kcz-A[i]));
for(res i=0;i<len;i++)A[i]=B[i]=0;
}
inline void getln(res *a,res *b,res lim){
derivation(a,D,lim),getinv(a,E,lim);
res qaq=0,len=1;
while(len<=lim)len<<=1,qaq++;
for(res i=0;i<len;i++)pos[i]=(pos[i>>1]>>1)|((i&1)<<(qaq-1));
NTT(D,1,len),NTT(E,1,len);
for(res i=0;i<len;i++)D[i]=1LL*D[i]*E[i]%kcz;
NTT(D,-1,len);
integral(D,b,lim);
for(res i=0;i<len;i++)D[i]=E[i]=0;
}
int F[N],G[N];
void getexp(res *a,res *b,res lim){
if(lim==1){b[0]=1;return;}
getexp(a,b,lim>>1);
getln(b,G,lim);
for(res i=0;i<lim;i++)F[i]=b[i],G[i]=Add(kcz-G[i],a[i]);
G[0]=Add(G[0],1);
res qaq=0,len=1;
while(len<=lim)len<<=1,qaq++;
for(res i=0;i<len;i++)pos[i]=(pos[i>>1]>>1)|((i&1)<<(qaq-1));
NTT(F,1,len),NTT(G,1,len);
for(res i=0;i<len;i++)F[i]=1LL*F[i]*G[i]%kcz;
NTT(F,-1,len);
for(res i=0;i<lim;i++)b[i]=F[i];
for(res i=0;i<len;i++)F[i]=G[i]=0;
}
inline void pre(){
inv[0]=inv[1]=1;
for(res i=2;i<=N-10;i++)inv[i]=1LL*inv[kcz%i]*(kcz-kcz/i)%kcz;
}
}
using namespace poly;
namespace Binom{
int fac[N],finv[N];
inline void pre(){
fac[0]=fac[1]=finv[0]=finv[1]=1;
for(res i=2;i<=N-10;i++)fac[i]=mul(fac[i-1],i),finv[i]=mul(finv[i-1],inv[i]);
}
inline int C(const res &x,const res &y){
return mul(fac[x],mul(finv[y],finv[x-y]));
}
}
int f[N],g[N],h[N];
inline void MAIN(){
pre(),Binom::pre();
res Case=read();
while(Case--){
n=read(),m=read(),r=Qpow(n,m,kcz-1);
for(res i=0;i<=m;i++)g[i]=Binom::C(i+n-1,n-1),f[i]=0;
res pw=2*qpow(m+1,n)-1,INV=qpow(pw);
f[0]=1;
for(res i=1;i<=m;i++)for(res j=i;j<=m;j+=i)add(f[j],mul(g[i],g[j/i]));
res l=1;
while(l<=m)l<<=1;
for(res i=1;i<=l;i++)f[i]=mul(f[i],INV);
getln(f,h,l);
res r0=qpow(n,m);
for(res i=0;i<=l;i++)h[i]=1LL*h[i]*r0%kcz,f[i]=0;
getexp(h,f,l);
pw=qpow(pw,r);
for(res i=0;i<=m;i++)printf("%d\n",mul(f[i],pw));
}
}
}

[1013 Penguin Love Tour]

solution

题面:一棵树,有边权点权,每个点能让一条与自己相邻的边的边权减去自己的点权,问最小直径。

做法:先考虑贪心,然后不会。于是考虑二分答案。设 ​ 表示 ​​ 点用在它的孩子边还是父边的子树内的最长路径。转移就是考虑合并最大链次大链,有二分在就很容易判断了。时间复杂度 ​。

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
//2021.8.13 by ljz
//email 573902690@qq.com
//if you find any bug in my code
//please tell me
const int N=1e5+10;
namespace MAIN{
int n,p[N];
int head[N],cnt;
struct E{
int next,to,val;
E() {}
E(res next,res to,res val):next(next),to(to),val(val) {}
}edge[N<<1];
inline void addedge(const res &u,const res &v,const res &w){
edge[++cnt]=E(head[u],v,w),head[u]=cnt;
edge[++cnt]=E(head[v],u,w),head[v]=cnt;
}
LL f[N][2];
//0 up 1 down
void dfs(res x,res fax,const RG LL &lim){
LL mx[3],mxc[2];
mx[0]=mx[1]=mx[2]=mxc[0]=mxc[1]=0;
for(res i=head[x];i;i=edge[i].next){
res tox=edge[i].to;
if(tox==fax)continue;
dfs(tox,x,lim);
RG LL v=min(f[tox][0]+max(0,edge[i].val-p[tox]),f[tox][1]+edge[i].val);
RG LL vc=min(f[tox][0]+max(0,edge[i].val-p[tox]-p[x]),f[tox][1]+max(0,edge[i].val-p[x]));
if(v>mx[0])mx[2]=mx[1],mx[1]=mx[0],mxc[1]=mxc[0],mx[0]=v,mxc[0]=vc;
else if(v>mx[1])mx[2]=mx[1],mx[1]=v,mxc[1]=vc;
else mx[2]=max(mx[2],v);
}
if(mx[0]+mx[1]>lim)f[x][0]=INF;
else f[x][0]=min(mx[0],INF);
if(mxc[0]+mx[1]>lim||mx[1]+mx[2]>lim)
if(mx[0]+mx[2]>lim||mx[0]+mxc[1]>lim)f[x][1]=INF;
else f[x][1]=mx[0];
else f[x][1]=min(max(mxc[0],mx[1]),INF);
f[x][1]=min(f[x][1],f[x][0]);
}
inline void MAIN(){
res Case=read();
while(Case--){
n=read(),cnt=0;
for(res i=1;i<=n;i++)p[i]=read(),head[i]=0;
for(res i=1;i<n;i++){
res u=read(),v=read(),w=read();
addedge(u,v,w);
}
RG LL l=0,r=(LL)1e10,ret=0;
while(l<=r){
RG LL mid=(l+r)>>1;
dfs(1,0,mid);
if(f[1][1]<INF)r=mid-1,ret=mid;
else l=mid+1;
}
printf("%lld\n",ret);
}
}
}