orangejuice's blog

挂了请务必叫我

CF1408 - Clusterization Counting

题目大意

给定 \(n\) 个点无向带权完全图,求将这些点分组,使得 组内的边边权 都小于 组内点连到组外点的边权

保证边权不同


分析

考虑如何确定合法的分组

从小到大依次加入每一条边,则一个合法的分组一定在某一个时刻满足

1.这个分组是一个极大的连通块

2.这个分组是一个团


dp

考虑类似 \(\text{Kruskal}\) 重构树的方法,对于合并的过程转化为树形结构

此时,分组决策只有两种

1.这个点儿子内部分别分组,背包合并

2.如果这个点恰好是合法的分组,那么选择这个点建立新的分组,并且此时儿子必须都是散点

借用树形背包的复杂度分析, \(dp\) 部分复杂度为 \(O(n^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


const int N=3010,P=998244353;

int n,m,k;
int F[N],S[N],C[N];
int Find(int x){
return F[x]==x?x:F[x]=Find(F[x]);
}
int dp[N][N/2];
int X[N*N/4],Y[N*N/4];

int main(){
n=rd();
rep(i,1,n) F[i]=i,S[i]=1,C[i]=0,dp[i][0]=dp[i][1]=1;
rep(i,1,n) rep(j,1,n) {
int x=rd();
if(i<j) X[x]=i,Y[x]=j;
}
rep(i,0,n*n) if(X[i]) {
int x=Find(X[i]),y=Find(Y[i]);
if(x==y) {
if(++C[x]==S[x]*(S[x]-1)/2) dp[x][1]++;
} else {
++n;
rep(a,0,S[x]) rep(b,0,S[y]) if((a>0)==(b>0)) dp[n][a+b]=(dp[n][a+b]+1ll*dp[x][a]*dp[y][b])%P;
F[x]=F[y]=n,S[n]=S[x]+S[y],C[n]=C[x]+C[y]+1,F[n]=n;
if(C[n]==S[n]*(S[n]-1)/2) dp[n][1]++;
}
}
rep(i,1,S[n]) printf("%d ",dp[n][i]);
}

CF1416F - Showing Off

题目大意

有一个网格图 \(A\) ,每个点有一个方向 \(D_{i,j}\) 和一个权值 \(A_{i,j}>0\) ,其中方向指向四联通的某一个点

定义其生成网格图为 \(B\) ,每个点的权值为这个点能够到达的点权值之和

现在给定 \(B\) ,构造一个 \(A\)


分析

每个点一条出边的图是基环内向树,环上的点 \(B_{i,j}\) 相同,树上点权值 \(B_{i,j}>B_{fa_{i,j}}\)

那么对于 \(B\) 中的点,如果能够找到周围一个 \(B_{i',j'}<B_{i,j}\) ,则可以认为 \((i,j)\) 已经确定了方向

否则,周围必须存在点 \(B'_{i,j}\)\(B_{i,j}\) 相同构成环

考虑网格图上不存在奇环,那么可以把环分解为若干极小的二元环,这样的构造是最优的

于是问题降低为二元,变成一个二分图匹配问题,具体的

CF1446F - Line Distance

题目大意

给定 \(n\) 个点 \(P_i\) ,在每个点对之间连一条线 \(P_iP_j\)

求所有线到原点距离的第 \(k\)


分析

这个 \(k\) 大问题的 \(k\)\(O(n^2)\) 级的,因此不是调整,可以考虑二分答案 \(L\)

考虑如何确定 \(d(O,P_iP_j)>L\) ,容易发现这等价于

\(P_iP_j\) 与圆 \(C:x^2+y^2=L^2\) 无交

假设当前确定了 \(P_i\) ,考虑什么时候 \(P_j\) 是合法的

img1

灰色线是切线,用蓝色区域标出了 \(d(O,P_iP_j)>L\) 的范围

直接统计蓝色区域的点并不容易,考虑对于这些点也作切线

Snipaste_2021-05-23_11-40-50.png

容易发现这些点的两个切点恰好被 \(P_i\) 的两个切点分隔开

通过求切点角度,转化为二维区间问题,用树状数组解决

复杂度为 \(O(n\log n\log x)\) ,其中 \(x\) 为精度范围

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
const int N=2e5+10,INF=1e9+10;
const db eps=1e-7,PI=acos((db)-1);

int n; ll m;
int X[N],Y[N];
struct Node{
db x,y;
bool operator < (const Node __) const {
return x<__.x;
}
} A[N];
db H[N];
int C,HC;

struct BIT{
int s[N],n;
void Init(int m){ n=m,memset(s,0,(n+1)<<2); }
void Add(int p,int x){
while(p<=n) s[p]+=x,p+=p&-p;
}
int Que(int p){
int res=0;
while(p) res+=s[p],p-=p&-p;
return res;
}
} T;

db norm(db x){
if(x<0) x+=2*PI;
if(x>=2*PI) x-=2*PI;
return x;
}
ll Calc(db L) {
C=HC=0;
rep(i,1,n) {
db D=sqrt(X[i]*X[i]+Y[i]*Y[i]);
if(L>=D) continue;
db x=atan2(Y[i],X[i]),y=acos(L/D);
db l=norm(x-y),r=norm(x+y);
if(l>r) swap(l,r);
A[++C]=(Node){l,r};
H[++HC]=l,H[++HC]=r;
}
sort(H+1,H+HC+1);
ll ans=1ll*n*(n-1)/2;
sort(A+1,A+C+1),T.Init(HC);
rep(i,1,C) {
A[i].x=lower_bound(H+1,H+HC+1,A[i].x)-H;
A[i].y=lower_bound(H+1,H+HC+1,A[i].y)-H;
ans-=T.Que(A[i].y)-T.Que(A[i].x);
T.Add(A[i].y,1);
}
return ans;
}

int main(){
n=rd(),m=rd<ll>();
rep(i,1,n) X[i]=rd(),Y[i]=rd();
db l=eps,r=2e4;
while(r>l*(1+eps)) {
db mid=(l+r)/2;
if(Calc(mid)<m) l=mid;
else r=mid;
}
printf("%.10lf\n",l);
}

CF1379F2 - Chess Strikes Back (hard version)

题目大意

给定一个 \(2n\times 2m\) 的交错棋盘,一个位置 \((i,j)\) 可以放当且仅当 \(2|i+j\)

给定 \(q\) 次操作,每次操作在一个位置加入或删除一个障碍

求是否存在一种方案能在棋盘上放入 \(nm\) 个互不攻击的国王(国王走九宫格)



分析

棋盘是交错的减少了很多无法处理的情况

容易发现,任何一个King都只能在它自己 \(2\times 2\) 的小方格内选择两种位置

如果一个 \(2\times 2\) 的单元左上角被填了,那么它就只能选择右下角

而根据这个King的互斥位置,在其右下方的所有King都只能选择右下角

如果右下角被占了同理,左上方的King只能选择左上角

实际上判定是否有解就是判定这些关系是否互斥,将每个点按照其所属 \(2\times 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
const int N=2e5+10,P=998244353;

int n,m,q;
int X[N],Y[N];
int A[N*20],B[N*20],C[N*20],T;
int ans;
void Add(int x,int y){
int t=x&1;
x=(x+1)/2,y=(y+1)/2;
if(t) {
for(int i=x;i<=n;i+=i&-i) ans+=Y[i]>=y;
for(int i=x;i<=n;i+=i&-i) if(X[i]>y) A[++T]=0,B[T]=i,C[T]=X[i],X[i]=y;
} else {
for(int i=x;i;i-=i&-i) ans+=X[i]<=y;
for(int i=x;i;i-=i&-i) if(Y[i]<y) A[++T]=1,B[T]=i,C[T]=Y[i],Y[i]=y;
}
}
void Back(){
if(A[T]==0) X[B[T]]=C[T];
else Y[B[T]]=C[T];
T--;
}

map <Pii,int> M;
vector <Pii> G[N<<2];
void Ins(int p,int l,int r,int ql,int qr,Pii x){
if(ql<=l && r<=qr) return G[p].pb(x);
int mid=(l+r)>>1;
if(ql<=mid) Ins(p<<1,l,mid,ql,qr,x);
if(qr>mid) Ins(p<<1|1,mid+1,r,ql,qr,x);
}

void Solve(int p,int l,int r){
int t1=T,t2=ans;
for(auto i:G[p]) Add(i.first,i.second);
if(l==r) puts(ans?"NO":"YES");
else {
int mid=(l+r)>>1;
Solve(p<<1,l,mid),Solve(p<<1|1,mid+1,r);
}
while(T>t1) Back();
ans=t2;
}

int main(){
memset(X,10,sizeof X);
n=rd(),m=rd(),q=rd();
rep(i,1,q) {
int x=rd(),y=rd();
Pii t(x,y);
if(M[t]==0) M[t]=i;
else Ins(1,1,q,M[t],i-1,t),M[t]=0;
}
for(auto i:M) if(i.second!=0) Ins(1,1,q,i.second,q,i.first);
Solve(1,1,q);
}

CF1450H2 - Multithreading (Hard Version)

题目大意

给定一个均分成 \(n\) 份( \(n\) 为偶数)的圆,每份上有一个元素为0/1,其中一些元素的值未知,且随机

当存在一个方案,0和0连线,1和1连线,使得每个元素都被恰好连一条线时,称环 \(c\) 合法

定义 \(f(c)\) 为上述连线方案中 不同色连线交叉的最小次数

同时需要支持修改元素,求 \(f(c)\) 的期望


贪心求解指定环

首先考虑一个Naive的贪心,在环上一旦出现相邻两点同色,就将他们连线然后删除

直到最后,就将变成01交替,设此时环长 \(n'\) ,考虑再让相邻的00,11连线

则得到交叉个数为 \(\frac{n'}{4}\)

这个贪心甚至连不带修的情况都做不了

简化求解

考虑上面贪心过程中被抵消的点

容易发现一定是一个奇数位置的点去抵消一个偶数位置的点

并且抵消之后其他位置的奇偶性保持不变

因此猜想最终剩下的黑点数量就是 \(|cnt_{odd}-cnt_{even}|\)

其中 \(cnt_{odd},cnt_{even}\) 表示已经确定的1元素在奇数/偶数位上的个数

也容易证明

根据贪心,显然同奇偶的点无法抵消,因此 \(ans\ge |cnt_{odd}-cnt_{even}|\)

而一旦存在两个不同奇偶的黑点,若他们不相邻

则他们之间一定存在一对相邻白点(否则奇偶性不对),进而不断合并白点使得它们相邻

白点可以对称得到相同值的式子,最终得到答案就是

\(\displaystyle \frac{|cnt_{odd}-cnt_{even}|}{2}\)


答案式子

设已经确定的部分 \(\delta=cnt_{odd}-cnt_{even}\) ,未确定的部分包含 \(x\) 个奇数位置, \(y\) 个偶数位置

则Naive的计算答案式子为

\(\displaystyle Sum=\sum_{i=0}^x \sum_{j=0}^y \frac{1}{2}\cdot [2|i-j+\delta] \cdot |\delta+i-j|\binom{x}{i}\binom{y}{j}\)

NTT

补上方案数 \(2^{x+y-1}\) (因为只有一半的方案奇偶性相同),用 \(y-j\) 代换 \(j\)

\(\displaystyle E=\frac{1}{2^{x+y}}\sum_{i=0}^x \sum_{j=0}^y \cdot [2|i-y+j+\delta] \cdot |\delta+i-y+j|\binom{x}{i}\binom{y}{j}\)

转换为 \(\displaystyle i+j\leftarrow \binom{x}{i}\binom{y}{j}\) 的形式后,带入组合意义合并 \(i,j\)

\(\displaystyle E=\frac{1}{2^{x+y}}\sum_{i=0}^{x+y} \cdot [2|\delta-y+i] \cdot |\delta-y+i|\binom{x+y}{i}\)

不妨设 \(\delta'=\delta-y\)

\(\displaystyle E=\frac{1}{2^{x+y}}\sum_{i=0}^{x+y} \cdot [\delta'\equiv i\pmod 2] \cdot |\delta'+i|\binom{x+y}{i}\)

根据 \(\delta'+i\) 的正负性容易确定一个范围,范围两边都是计算都转化为

\(\displaystyle S(n,m)=\sum _{i=0}^m [2\not |i]\cdot i\cdot \binom{n}{i}\)

\(\displaystyle S(n,m)=\sum _{i=0}^m [2\not |i]\cdot n\cdot \frac{(n-1)!}{(n-i)!(i-1)!}\)

\(\displaystyle S(n,m)=n\sum _{i=0}^{m-1} [2 |i]\cdot \binom{n-1}{i}\)

形如 \(\displaystyle m|2, S'(n,m)=\sum _{i=0}^m [2|i]\cdot \binom{n}{i}\) ,可以转化为

\(\displaystyle S'(n,m)=\sum _{i=0}^m [2|i](\binom{n-1}{i}+\binom{n-1}{i-1})\)

\(\displaystyle S'(n,m)=\sum _{i=0}^m \binom{n-1}{i}\)

组合数关于 \(m\) 一维的前缀和是一个经典的步移问题

\(S(n,m-1)=S(n,m)-C(n,m)\)

\(S(n,m+1)=S(n,m)+C(n,m+1)\)

\(S(n+1,m)=\displaystyle \sum_{i=0}^m C(n+1,m)=\sum_{i=0}^mC(n,i)+\sum_{i=0}^{m-1}C(n,i-1)=2S(n,m)-C(n,m)\)

\(\displaystyle S(n-1,m)=\frac{S(n,m)+C(n-1,m)}{2}\)

封装一下计算即可,复杂度为 \(O(n)\)

真的只是一点点麻烦

QQ图片20210506191744.jpg
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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}

const int N=2e5+10,P=998244353;

int n,m;
int I[N],J[N];
int P1[N],P2[N];
ll qpow(ll x,ll k=P-2){
ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}

char s[N];
int d,x,y;

int Pow2(int x){ return x<0?P2[-x]:P1[x]; }
int C(int n,int m){ return n<0||m<0||n<m?0:1ll*J[n]*I[m]%P*I[n-m]%P; }

int p1,p2,cur=1;
// 组合数关于m的前缀和,步移计算
int SC(int n,int m) {
if(n<0||m<0) return 0;
if(m==0) return 1;
if(m>=n) return Pow2(n);
if(m==n-1) return Pow2(n)-1;

/* Brute Force
int sum=0;
rep(i,0,m) sum=(sum+C(n,i))%P;
return sum;
*/

/* assertions blows
static int fl=1;
assert(fl || abs(p1-n)+abs(p2-m)<=10);
fl=0;
*/

while(p2>m) cur=(cur-C(p1,p2--))%P;
while(p2<m) cur=(cur+C(p1,++p2))%P;
while(p1<n) cur=(cur*2ll-C(p1++,p2))%P;
while(p1>n) cur=1ll*(cur+C(--p1,p2))*(P+1)/2%P;
return cur;
}

// T 指前面的S'
int T(int n,int m,int k){ return k==1?(SC(n,m)-T(n,m,0))%P:(n==0?m>=0:SC(n-1,m-(m&1))); }
int T(int n,int l,int r,int k){

/*Brute Force
int sum=0;
rep(i,l,r) if((i&1)==k) sum=(sum+C(n,i))%P;
return sum;
*/

return l>r?0:(T(n,r,k)-T(n,l-1,k))%P;
}

int S(int n,int m){ return 1ll*n*T(n-1,m-1,0)%P; }
int S(int n,int l,int r,int k=1){

/*Brute Force
int sum=0;
rep(i,l,r) if((i&1)==k) sum=(sum+1ll*i*C(n,i))%P;
return sum;
*/

if(l>r) return 0;
if(k==0) return (1ll*n*(SC(n-1,r-1)-SC(n-1,l-2))-S(n,l,r))%P;
return (S(n,r)-S(n,l-1))%P;
}

int Que(){
int D=d-y,n=x+y,ans=0;
/* Brute Force
rep(i,0,n) if((i&1)==(D&1)) {
ans=(ans+1ll*abs(D+i)*C(n,i))%P;
}
*/
if(D<0) {
int t=-D-1;
ans=(ans-1ll*D*T(n,t,D&1))%P;
ans=(ans-S(n,0,t,D&1))%P;
}
if(D+n>=0) {
ans=(ans+1ll*D*T(n,max(0,-D),n,D&1))%P;
ans=(ans+S(n,max(0,-D),n,D&1))%P;
}
ans=1ll*(ans+P)*Pow2(-n)%P;
return ans;
}

int main(){
rep(i,*P1=1,N-1) P1[i]=P1[i-1]*2,Mod1(P1[i]);
rep(i,*P2=1,N-1) P2[i]=((P2[i-1]&1)?P2[i-1]+P:P2[i-1])/2;
rep(i,*J=1,N-1) J[i]=1ll*J[i-1]*i%P;
I[N-1]=qpow(J[N-1]);
drep(i,N-1,1) I[i-1]=1ll*I[i]*i%P;
n=rd(),m=rd(),scanf("%s",s+1);
rep(i,1,n) {
if(s[i]=='b') i&1?d++:d--;
if(s[i]=='?') i&1?x++:y++;
}
printf("%d\n",Que());
while(m--) {
int i=rd(),c=getchar();
if(s[i]=='b') i&1?d--:d++;
if(s[i]=='?') i&1?x--:y--;
s[i]=c;
if(s[i]=='b') i&1?d++:d--;
if(s[i]=='?') i&1?x++:y++;
printf("%d\n",Que());
}
}

CF1411F - The Thorny Path

题目大意

给定一个置换 \(p_i\) ,求通过最少次交换 \(p_i,p_j\) ,使得最终的置换中所有置换环 \(size\) 乘积最大


分析

一个常规结论:

对于 \(n(n\ge 3)\) 的拆分 \(n=\sum_{i=1}^m a_i\) ,最大化 \(\prod a_i\) ,最优情况下

  1. \(n\mod 3=0\)\(a_i=3\)

  2. \(n\mod 3=2\)\(i<m,a_i=3 ; a_m=2\)

  3. \(n\mod 3=1\)\(i<m,a_i=3 ; a_m=4\)\(i<m-1,a_i=3 ;a_{m-1}=a_m=2\)

简要证明


容易发现对于任意 \(n\) ,最终 \(a_i\) 的方案是 \(O(1)\) 的,设当前置换环为 \(b_i\) ,我们需要操作 \(b_i\) 变成 \(a_i\)

1.一次在同环交换可以分裂一个环

2.一次异环交换合并两个环

所以原问题实际上就是最少次数分裂合并 \(b_i\)

对于 \(n\mod 3=0\)\(n\mod 3=2\) 的情况,如果当前 \(b_i\ge 3\) ,可以一直不停分裂

最终剩下的就是 \(b'_i=1\) 或者 \(b'_i=2\)

对于 \(n\mod 3=2\) 的情况,优先从中取出一个2<或者由两个1合并得到一个2

剩下的优先合并1和2,然后剩下的自己合并

\(n\mod 3=1\) 同理,但是 \(a_i=4\) 的情况也不能分裂,需要拿出来特殊处理

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
const int N=1e6+10,P=1e9+7;

int n,m;
int Pow[N];
int A[N],L[N],C,R[N],D;
int vis[N];

int Calc(int c1,int c2){
int t=min(c1,c2),ans=t;
c1-=t,c2-=t;
if(c1) ans+=c1/3*2;
if(c2) ans+=c2;
return ans;
}

int Calc(int c1,int c2,int c4){ return c4+Calc(c1+c4,c2); }

int main(){
rep(i,*Pow=1,N-1) Pow[i]=1ll*Pow[i-1]*3%P;
rep(_,1,rd()) {
n=rd();
rep(i,1,n) A[i]=rd(),vis[i]=0;
C=0;
rep(i,1,n) if(!vis[i]) {
int c=0;
for(int j=i;!vis[j];j=A[j]) c++,vis[j]=1;
L[++C]=c;
}
if(n%3==0) {
printf("%d ",Pow[n/3]);
int ans=0;
rep(i,1,C) {
while(L[i]>3) L[i]-=3,ans++;
if(L[i]==3) L[i]=0;
}
int c1=0,c2=0;
rep(i,1,C) if(L[i]==1) c1++;
else if(L[i]==2) c2++;
ans+=Calc(c1,c2);
printf("%d\n",ans);
continue;
}

if(n%3==2) {
printf("%d ",Pow[n/3]*2%P);
int cnt=n/3,ans=0;
rep(i,1,C) {
while(cnt && L[i]>3) L[i]-=3,cnt--,ans++;
if(L[i]==3 && cnt) L[i]=0,cnt--;
}
int c1=0,c2=0;
rep(i,1,C) if(L[i]==1) c1++;
else if(L[i]==2) c2++;
if(c2) c2--;
else c1-=2,ans++;
ans+=Calc(c1,c2);
printf("%d\n",ans);
continue;
}
printf("%lld ",Pow[(n-4)/3]*4ll%P);
int cnt=(n-4)/3,c3=0,ans=0;
rep(i,1,C) {
while(cnt && L[i]>4) L[i]-=3,cnt--,ans++;
if(L[i]==3 && cnt) L[i]=0,cnt--;
}
int c1=0,c2=0,c4=0;
rep(i,1,C) if(L[i]==1) c1++;
else if(L[i]==2) c2++;
else if(L[i]==3) c3++;
else if(L[i]==4) c4++;
if(c3) ans++;
else {
int w=1e9;
if(c4) cmin(w,Calc(c1,c2,c4-1));
if(c1>=4) cmin(w,Calc(c1-4,c2,c4)+2);
if(c2>=2) cmin(w,Calc(c1,c2-2,c4));
if(c1>=2 && c2) cmin(w,Calc(c1-2,c2-1,c4)+1);
ans+=w;
}
printf("%d\n",ans);
}
}

CF1452G - Game On Tree

题目大意

A和B在树上Van游戏,每个人操作一些点

A操作一个点 \(i\) ,B操作一个点集 \(a_j\)

每轮A,B分别进行操作,可以对于自己的所有点任意移动1步或0步

在某一轮,当A的点碰到B的点时游戏结束

A希望尽量迟结束,B希望尽量早结束

给定B的初始点集 \(a_j\) ,对于A的每个初始点 \(i\) 判断多少轮结束


分析

由于B的操作显然是不停向A收缩直到碰到

那么可以广搜求出每个点原地不动时被B干掉的时间 \(F_i\)

那么考虑A的移动过程,每一步可以到达一个点 \(u\)

必须满足在第 \(i\) 步所在的点 \(u\)\(F_u>i\) ,否则结束游戏

对于初始节点 \(u\) ,不妨设最终结束的节点为 \(t\) ,我们希望一路跑到 \(t\) 然后站住不动,此时答案就是 \(F_t\)

而实际上,任何一个点 \(u\) 能够跑到 \(t\) ,等价于 \(dis(u,t)<F_t\)

Proof:

由最短路三角不等式可知

\(\forall (u,v)\in Tree, dis_{v}-1\leq dis_u\leq dis_{v}+1\)

\(dis_e\) 在树的路径上连续变化,不妨设移动路径为 \(p_i,i\in[1,k],p_k=t,k\leq F_t\)

若能在 \(F_t-1\) 的时间内到达 \(p_k\) ,那么必然能在 \(F_t-2\) 的时间内到达 \(p_{k-1}\)

进而归纳得到


那么问题变成了,对于每个点 \(u\) ,向周围 \(F_u-1\) 范围内的点对于 \(F_u\)\(\max\)

容易点分治处理,复杂度为 \(O(n\log n)\)

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
const int N=2e5+10,INF=1e9+10;

int n,F[N],A[N];
vector <int> G[N];
queue <int> que;

int mi,rt,sz[N],vis[N];
void FindRt(int n,int u,int f){
int ma=0; sz[u]=1;
for(int v:G[u]) if(!vis[v] && v!=f) {
FindRt(n,v,u),sz[u]+=sz[v];
cmax(ma,sz[v]);
}
cmax(ma,n-sz[u]);
if(mi>ma) mi=ma,rt=u;
}

int dep[N],id[N],c,s[N];
void dfs(int u,int f){
id[++c]=u;
for(int v:G[u]) if(v!=f && !vis[v]) {
dep[v]=dep[u]+1;
dfs(v,u);
}
}
void Div(int n,int u){
mi=1e9,FindRt(n,u,0),u=rt,vis[u]=1;
c=0,dep[u]=0,dfs(u,0);
rep(i,0,c) s[i]=0;
rep(i,1,c) {
int u=id[i];
if(F[u]>dep[u]) cmax(s[min(c,F[u]-1-dep[u])],F[u]);
}
drep(i,c-1,0) cmax(s[i],s[i+1]);
rep(i,1,c) cmax(A[id[i]],s[dep[id[i]]]);
for(int v:G[u]) if(!vis[v]) {
if(sz[v]>sz[u]) sz[v]=n-sz[u];
Div(sz[v],v);
}
}

int main(){
rep(i,2,n=rd()){
int u=rd(),v=rd();
G[u].pb(v),G[v].pb(u);
}
rep(i,1,n) F[i]=-1;
rep(i,1,rd()) {
int x=rd();
F[x]=0,que.push(x);
}
while(!que.empty()) {
int u=que.front(); que.pop();
for(int v:G[u]) if(F[v]==-1)
F[v]=F[u]+1,que.push(v);
}
Div(n,1);
rep(i,1,n) printf("%d ",A[i]);
}

CF1468L - Prime Divisors Selection

题目大意

对于一个序列 \(A\) ,一个合法的质因子序列 \(P\) 满足 \(\forall P_i|A_i,P_i\ is\ a\ prime\)

给定一个序列 \(a_i,i\in[1,n]\) ,求选出 \(k\) 个数,使得对于选出的序列 \(A\)

不存在一个 \(P\) 使得 \(P\) 中某个元素恰好出现一次

\(n\leq 1000,a_i\leq 10^{18}\)


分析

由题目的意思我们知道肯定要分解质因数

Pollard's_Rho!!!

\(10^{18}\) 分解质因数可不是开玩笑的。。。

所以先考虑合法 \(A\) 的判定

判定 \(A\) 合法

假设可以存在一个元素恰好出现一次 \(x\) ,那么在 \(A\) 所有元素质因子中至少要包含一个 \(x\)

并且,不存在两个元素只包含 \(x\)

也就是说,对于合法的 \(A\) 中出现的所有质因子 \(x\) ,都必须存在两个元素只包含 \(x\)

我们称只包含 \(x\) 作为质因子的元素为 \(x\) -元素,为了构造合法的 \(A\) ,我们必须对于一些 \(x\) 选出若干对 \(x\) -元素

对于每个 \(x\) ,我们只把前两个 \(x-\) 元素视为有效,假设有 \(c\) 对这样的元素

那么情况分几种

  1. \(2|k,2c\ge k\) ,那么直接随意选完即合法

  2. \(2c\ge k,2\not |k\) ,此时我们需要选出部分对,使得剩下的元素中存在一个数它的因子集已经被选

枚举剩下一个元素,判定合法即可

  1. \(2c\leq k\) ,此时可以将 \(c\) 对全部选出,判断是否还存在 \(k-2c\) 个可以选择即可


质因数分解

亲身试验,我的Pollards_Rho它T飞了

容易发现,对于 \(x-\) 元素我们只需要找到它的 \(a_i=x^k\)

对于其他元素我们只需要找到 \(a_i\) 对应的 \(x\) 的集合,或者判断无法被 \(x-\) 元素集合包含

由于 \(n\leq 1000\) ,我们可以先得到 \(x-\) 元素集合,其他元素我们最后一个个判定

找到 \(a_i=x^k\) 问题简化了很多

如果你相信std::pow,可以直接来

只需要找到一个最小的 \(k'\) ,使得 \(a_i=x'^{k'}\) ,判定 \(x'\) 是否为质数,如果是则停止,否则继续分解 \(x'\)

对于 \(k'\leq 3\) ,甚至更大一些的情况,std::pow比较可信

\(k'>3\) 的情况(实际上 \(k'=4\)\(k'=2\) 包含,所以是 \(k'\ge 5\)

实际上 \(x'\) 已经很小了,直接枚举质数即可

素数判定依然需要 \(\text{Miller_Rabin}\)但是至少不用Pollards_Rho了

CodeForces Submission

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
const int N=1e5+10;

int n,m;
int pri[N],pc,notpri[N];

ll qmul(ll x,ll y,ll P){
ull z=(long double)x/P*y+0.5;
ll res=(ull)x*y-z*P; Mod2(res);
return res;
}
ll qpow(ll x,ll k,ll P){
ll res=1;
for(;k;k>>=1,x=qmul(x,x,P)) if(k&1) res=qmul(res,x,P);
return res;
}

int Miller_Rabin(ll n){
if(n<N) return !notpri[n];
if(~n&1) return 0;
ll s=n-1,t=0;
while(s%2==0) s/=2,t++;
rep(k,1,7) {
ll a=qpow(pri[rand()%pc+1],s,n),b;
rep(i,1,t) {
b=qmul(a,a,n);
if(b==1 && a!=1 && a!=n-1) return 0;
a=b;
}
if(a!=1) return 0;
}
return 1;
}

ll a[N],mk[N];
vector <ll> F[N]; // Factor Set of each element
vector <ll> IF; // Independent Factor Set
void unique(vector <ll> &a){ sort(a.begin(),a.end()),a.erase(unique(a.begin(),a.end()),a.end()); }

map <ll,vector<int> > M;
ll ans[N];
void Outp(){
rep(i,1,m) ans[i]=a[ans[i]];
sort(ans+1,ans+m+1);
rep(i,1,m) printf("%lld ",ans[i]);
exit(0);
}

ll Root2(ll n){
ll x=round(sqrt(n));
return x*x==n?x:-1;
}
ll Root3(ll n){
ll x=round(pow(n,1./3));
return x*x*x==n?x:-1;
}

ll KDivide(ll x){
if(Miller_Rabin(x)) return x;
ll y;
if(~(y=Root2(x))) return KDivide(y);
if(~(y=Root3(x))) return KDivide(y);
ll U=pow(x,1./5)+4;
vector <ll> fac;
for(int i=1;pri[i]<=U;++i) if(x%pri[i]==0) {
while(x%pri[i]==0) x/=pri[i];
fac.pb(pri[i]);
}
if(fac.size()==1 && x==1) return fac[0];
return -1;
}


int main(){
rep(i,2,N-1) if(!notpri[i]) {
pri[++pc]=i;
for(int j=i+i;j<N;j+=i) notpri[j]=1;
}
n=rd(),m=rd();
rep(i,1,n) {
ll x=KDivide(a[i]=rd<ll>());
if(~x) IF.pb(x);
}
unique(IF);
rep(i,1,n) {
ll x=a[i];
for(ll y:IF) if(x%y==0) {
while(x%y==0) x/=y;
F[i].pb(y);
}
if(x>1) F[i].pb(-1),F[i].pb(-2); // invalid factor, emm... to avoid some situation we push two
if(F[i].size()==1 && M[F[i][0]].size()<2) M[F[i][0]].pb(i),mk[i]=1;
}
int c=0;
for(auto i:M) if(i.second.size()>=2) c++;
if(m%2==0 && c*2>=m) {
// choose k/2 pairs!!
int k=m;
for(auto i:M) if(i.second.size()>=2) {
if(!k) break;
rep(j,0,1) ans[k--]=i.second[j];
}
Outp();
}
if(c*2>=m) {
// find another
rep(i,1,n) if(!mk[i] && (int)F[i].size()<=m/2) {
int f=1;
for(ll x:F[i]) if(M[x].size()<2) f=0;
if(f) {
int k=m;
ans[k--]=i;
for(ll x:F[i]) {
rep(j,0,1) ans[k--]=M[x][j];
M.erase(x);
}
for(auto i:M) if(i.second.size()>=2) {
if(!k) break;
rep(j,0,1) ans[k--]=i.second[j];
}
Outp();
}
}
} else {
int k=m;
for(auto i:M) if(i.second.size()>=2) {
if(!k) break;
rep(j,0,1) ans[k--]=i.second[j];
}
// Count if we have left much enough...
rep(i,1,n) if(!mk[i]) {
if(!k) break;
int f=1;
for(ll x:F[i]) if(M[x].size()<2) f=0;
if(f) ans[k--]=i;
}
if(!k) Outp();
}
puts("0");
}

CF1456E - XOR-ranges

题目大意

\(n\) 个二进制数 \(a_i\in[L_i,R_i]\) ,给定每个二进制位的权值

序列 \(a_i\) 的权值就是 \(a_i\oplus a_{i+1}\) 二进制为权值之和

求所有满足 \(a_i\in[L_i,R_i]\) 的最小权值


分析

显然需要我们考虑对于一个数进行 数位 \(dp\) 的过程

从高位到低位,一个数要么最终都一直被限制着,要么在两个不同的位置分别解除了 \(L_i,R_i\) 的限制

容易发现, \(L_i,R_i\) 中某一个先被解除的限制一定是在第一个 \(\text{bit}(L_{i},p)\ne \text{bit}(R_i,p)\) 的位置 (实际上是小于号)

此后,选择的数一直跟着剩下的限制直到下一个位置解除

不妨考虑 \(L_i,R_i\) 中限制时间较长的一个限制,设在 \(p\) 这一位解除,那么

  1. \(\exists k<p,\text{bit}(L_i,k)\ne \text{bit}(R_i,k)\)

2.如果是 \(R_i\) ,那么 \(\text{bit}(R_i,p)=1,\text{bit}(a_i,p)=0\)

如果是 \(L_i\) ,那么 \(\text{bit}(R_i,p)=0,\text{bit}(a_i,p)=1\)

如果最终每个数解除限制的位置如下

QQ截图20210519114449.png

考虑他们如何对于答案贡献

对于每个二进制位,如果存在空白段,空白段的二进制可以跟随左边的段或者右边的段改变

当左边和右边最邻近的两个数这一位不同,则产生贡献

因此考虑依次扫描每一个二进制位,找到相邻可能产生贡献的 \((a_l,a_r)\)

从低位到高位,这就是一个不断将 \((a_l,a_r)\) 分裂为 \((a_l,a_k),(a_k,a_r)\) 的过程

也就是一个 笛卡尔树上的区间dp

对于当前二进制位 \(p\) 和数对 \(a_l,a_r\) ,我们需要知道的是

\(a_l\) 是受到 \(L_l\) 还是 \(R_l\) 的限制,且是否 \(p\) 这一位它解除了限制 (因为解除贡献的这一位与 \(L_l / R_l\) 相反)

\(a_r\) 是同理

转移可以直接进入下一个二进制位,计算 \(a_l,a_r\) 的贡献

或者分裂区间枚举中点 \(k\)\(a_k\) 恰好在这一位解除限制(或者 \(a_k\) 一直都没有解除限制,此时 \(p=0\)

此时 \(L_k,R_k\) 必然满足前面提到的限制,并且根据 \(\text{bit}(L_k,p)\)\(\text{bit}(R_k,p)\) 枚举 \(k\) 受到 \(L_k\) 或者 \(R_k\) 的限制

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
const int N=55;

int n,k;
ll L[N][2],C[N];
ll dp[N][N][N][2][2][2][2];
int bit(ll x,int p){ return (x>>p)&1; }
ll dfs(int p,int l,int r,int f,int x,int g,int y){
if(p==k) return r-l==1?0:1e18;
ll &res=dp[p][l][r][f][x][g][y];
if(~res) return res;
res=dfs(p+1,l,r,f,0,g,0)+(l && r<=n && (x^y^bit(L[l][f]^L[r][g],p)))*C[p];
rep(k,l+1,r-1) {
// a[k] is limited all time
if(!p) rep(j,0,1) cmin(res,dfs(p,l,k,f,x,j,0)+dfs(p,k,r,j,0,g,y));
// a[k] frees at p
if((L[k][0]^L[k][1])>>(p+1)) { // L,R has some different bits before p
rep(j,0,1) if(bit(L[k][j],p)==j)
cmin(res,dfs(p,l,k,f,x,j,1)+dfs(p,k,r,j,1,g,y));
}
}
return res;
}

int main(){
scanf("%d%d",&n,&k);
rep(i,1,n) rep(j,0,1) scanf("%lld",L[i]+j);
rep(i,0,k-1) scanf("%lld",C+i);
memset(dp,-1,sizeof dp);
printf("%lld\n",dfs(0,0,n+1,0,0,0,0));
}

CF1477E - Nezzar and Tournaments

题目大意

有两队人 \(a_i,i\in[1,n],b_j,j\in[1,m]\) ,现在把他们放在一起排成一行 \(c_i\)

顺次给每个人计分,初始 \(s_0=k\)

\(s_i=\max\{0,s_{i-1}+c_i-c_{\max\{i-1,1\}}\}\)

现在要最大化每个 \(a_i\) 所在位置的 \(s_i\) 之和 与 \(b_i\) 所在 \(s_i\) 之和 的差

支持修改和对于不同 \(k\) 查询


分析

考虑 \(k=0\) 简单情况

1.若 \(s_i\) 不清零,则 \(s_i=c_i-lst\) ,其中 \(lst\) 表示上一个被清零位置的 \(c_j\)

  1. \(s_i\) 清零,则 \(c_i<lst\)

容易发现, \(\displaystyle s_i=c_i-\min_{j\leq i}\{ c_j\}\)


那么对于含 \(k\) 的情况,类似可以得到

\(\displaystyle s_i=k-c_1+c_i+\max\{0,c_1-k-\min_{j\leq i}\{c_j\}\}\)

假设我们固定了一个 \(c_1\) ,现在考虑对于剩下的 \(a_i,b_j\) 排出一个最优的排列

容易发现, \(k-c_1+c_i\) 的贡献时固定的,只有前缀最小值会影响答案

我们希望对于 \(b_i\) ,前缀最小值较大, \(a_i\) 反之

那么容易发现可以先降序排列 \(b_j\) ,再正序排列 \(a_i\)

此时 \(b_{\min}\) 可以贡献给 \(a_i\) 的前缀最小值,同时 \(b_j\) 的前缀最小值能够取到最大


此时,不妨设 \(c_1=t\)\(\min\{a_i,b_i\}=Min\)

\(\min\{c_j\}=c_1\) 时, \(\max\) 里的东西没有贡献,故可以得到

1.对于每个 \(a_i\) ,若它没有被放在 \(c_1\) ,则贡献 \(k-t+a_i+\max\{0,t-k-Min\}\)

2.对于每个 \(b_i\) (不特殊考虑第一个),则贡献 \(-(k-t+b_i+\max\{0,t-k-b_i\})\) (忽略最小值为 \(t\) 的情况)

则最终式子为

\(\displaystyle f(t)=(n-[t\in a_i])\cdot \max\{0,t-k-Min\}-\sum \max\{0,t-k-b_i\}+(m-n)t+C\)

其中 \(C=(n-m)k+\sum a_i-\sum b_i\)

容易发现 \(f(t)\) 是关于 \(t\) 的分段一次函数,根据斜率变化情况分析,极值位置仅 \(O(1)\)

那么对于 \(a_i\) 作为 \(t\)\(b_j\) 作为 \(t\) 的情况,分别计算 \(f(t)\) 的极值位置

极值位置需要一个 \(k\) 大查询和 \(\text{lower_bound}\)

计算 \(f(t)\) 需要一个前缀查询

我用 \(\text{BIT}\) 充当平衡树来维护,复杂度为 \(O((n+m+q)\log 10^6)\)

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
const int N=1e6+10,INF=1e9+10;

int n,m,q;
int a[N],b[N];
struct BIT{
ll s[N];
int c[N],n;
void Init(int m){ n=m; }
void Add(int p,int x,int y){
p++;
while(p<N) s[p]+=x,c[p]+=y,p+=p&-p;
}
ll Que(int p){
p++;
if(p<=0) return 0;
ll sum=0,cnt=0,t=p-1;
while(p) sum+=s[p],cnt+=c[p],p-=p&-p;
return t*cnt-sum;
}
int Rank(int p){
if(p<0) return 0; // 一些奇怪的边界特判 ,防止查询越界
p++,cmin(p,N-1);
int res=0;
while(p) res+=c[p],p-=p&-p;
return res;
}
int Kth(int k){ // 注意一定要避免找到并不存在的数值
cmin(k,n),cmax(k,1);
int p=0;
drep(i,19,0) if(p+(1<<i)<N && c[p+(1<<i)]<k) k-=c[p+=1<<i];
return p;
}
int Prev(int x) { return Kth(Rank(x)); }
int Next(int x) { return Kth(min(n,Rank(x)+1)); }
} A,B;

ll delta;
void AddA(int x,int k){
delta+=x*k;
A.Add(x,x*k,k);
}
void AddB(int x,int k){
delta-=x*k;
B.Add(x,x*k,k);
}

ll QueA(ll k){
ll Min=min(A.Kth(1),B.Kth(1));
auto F=[&](ll t){ return (n-1)*max(0ll,t-k-Min)-B.Que(t-k)+(m-n)*t; };
ll ans=max(F(A.Kth(1)),F(A.Kth(n)));
int p=B.Kth(m-1)+k;
cmax(ans,F(A.Prev(p))),cmax(ans,F(A.Next(p)));
return ans;
}

ll QueB(ll k){
ll Min=min(A.Kth(1),B.Kth(1));
auto F=[&](ll t){ return n*max(0ll,t-k-Min)-B.Que(t-k)+(m-n)*t; };
ll ans=max(F(B.Kth(1)),F(B.Kth(m)));
int p=B.Kth(m)+k;
cmax(ans,F(B.Prev(p))),cmax(ans,F(B.Next(p)));
return ans;
}

ll Que(ll k){
return max(QueA(k),QueB(k))+delta+(n-m)*k;
}

int main(){
n=rd(),m=rd(),q=rd(),A.Init(n),B.Init(m);
rep(i,1,n) AddA(a[i]=rd(),1);
rep(i,1,m) AddB(b[i]=rd(),1);
while(q--) {
int opt=rd();
if(opt==1) {
int x=rd(),y=rd();
AddA(a[x],-1),AddA(a[x]=y,1);
} else if(opt==2) {
int x=rd(),y=rd();
AddB(b[x],-1),AddB(b[x]=y,1);
} else printf("%lld\n",Que(rd()));
}
}
0%