题目:
Reunion
三角形
异或图
shallot加强版
Clamp Paths on Tree
Permutation Deletion
金枪鱼付款
金枪鱼食用
Drzewo czerwono-czarne
Desant 2
Fiolki 2
要算期望,就算所有可能的 r r r 的半径不小于 r r r 的概率然后累和。进而转化为算小于 r r r 的方案数。一种方案的半径小于 r r r,等价于从任意有人的点出发半径 r r r 内不全有人,等价于从所有没人的点出发半径 r r r 的区域并集为全集。
设 f i , j f_{i,j} fi,j 表示以 i i i 为根可以向外延伸距离为 j j j 的方案数。当 j j j 为负数是表示需要外部的点向内延伸。注意到一个子树的方案对应的 j j j 是唯一的,即不会既需要外部的点又可以向外延伸。做树上背包即可。
时间复杂度 O ( n 3 ) O(n^3) O(n3),空间复杂度 O ( n 2 ) O(n^2) O(n2)
#include
#include
using namespace std;
#define R register int
#define N 301
#define P 998244353
vector<int>G[N];
int f[N][603],g[603];
inline void Merge(int x,int y,const int r){
vector<int>A,B;
for(int i=-r;i<=r;i++){
g[N+i]=0;
if(f[x][N+i]!=0){
A.push_back(i);
}
if(f[y][N+i]!=0){
B.push_back(i);
}
}
for(int T1:A){
for(int T2:B){
int t;
if(T1+T2<0){
t=T1<T2-1?T1:T2-1;
}else{
t=T1>T2-1?T1:T2-1;
}
g[N+t]=((long long)f[x][N+T1]*f[y][N+T2]+g[N+t])%P;
}
}
for(R i=-r;i<=r;i++){
f[x][N+i]=g[N+i];
}
}
inline void DP(int x,int F,const int r){
for(R i=-r;i<=r;i++){
f[x][N+i]=0;
}
f[x][N-1]=f[x][N+r]=1;
for(int T:G[x]){
if(T!=F){
DP(T,x,r);
Merge(x,T,r);
}
}
}
int main(){
int n,x,y,ans,pw=1,ipw=1;
scanf("%d",&n);
for(R i=1;i!=n;i++){
scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
for(R i=0;i!=n;i++){
if((ipw&1)==1){
ipw+=P;
}
ipw>>=1;
pw<<=1;
if(pw>P){
pw-=P;
}
}
ans=(n-1ll)*pw%P;
for(R i=1;i!=n;i++){
DP(1,0,i);
for(R j=0;j<=i;j++){
ans-=f[1][N+j];
if(ans<0){
ans+=P;
}
}
}
printf("%d",(long long)ipw*ans%P);
return 0;
}
注意到操作可逆,所求的是要历史最大值最小。设状态 ( s , m ) (s,m) (s,m) 表示当前石子数为 s s s,历史最大值为 m m m。每个点的操作也可以这样的形式。可以得到操作 ( a 1 , b 1 ) , ( a 2 , b 2 ) (a_1,b_1),(a_2,b_2) (a1,b1),(a2,b2) 的操作优先级比较方式:
每次取出最优的操作,和父亲联通块合并,表示执行完父亲联通块的操作后立即执行该点的操作。用堆、并查集和链表即可。最后由于子树间的操作互不影响,操作具有结合律,因此按序执行对祖先修改即可。
时间复杂度 O ( n log 2 2 n ) O(n \log_2^2 n) O(nlog22n),空间复杂度 O ( n ) O(n) O(n)
#include
#include
#include
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 200001
struct Item{
L SumD,MaxV;
I friend bool operator<(Item A,Item B){
bool a=A.SumD>0,b=B.SumD>0;
if(a!=b){
return a<b;
}
if(a==true){
return A.MaxV-A.SumD<B.MaxV-B.SumD;
}
return A.MaxV<B.MaxV;
}
I void operator+=(Item B){
L c=SumD+B.MaxV;
if(c>MaxV){
MaxV=c;
}
SumD+=B.SumD;
}
}opt[N],Lazy[800000];
struct Node{
Item Mod;
int Size,Id;
I friend bool operator<(Node A,Node B){
return B.Mod<A.Mod;
}
};
I Node Pair(int x,int s,L sum,L maxv){
Node res;
res.Id=x;
res.Size=s;
res.Mod.MaxV=maxv;
res.Mod.SumD=sum;
return res;
}
vector<int>G[N];
int w[N],f[N],Top[N],Next[N],ref[N],h[N],End[N],sz[N],dfn[N],ct;
L sum[N];
class DisjiontSet{
int fa[N],s[N];
public:
I void Init(const int n){
for(R i=1;i<=n;i++){
fa[i]=i;
s[i]=1;
}
}
I int GetF(int x){
if(fa[x]==x){
return x;
}
fa[x]=GetF(fa[x]);
return fa[x];
}
I int GetSize(int x){
x=GetF(x);
return s[x];
}
I void Merge(int x,int y){
x=GetF(x);
y=GetF(y);
s[x]+=s[y];
fa[y]=x;
}
}DS;
I void PreDFS(int x){
sz[x]=1;
for(int T:G[x]){
PreDFS(T);
sz[x]+=sz[T];
if(sz[T]>sz[h[x]]){
h[x]=T;
}
}
}
I void ReDFS(int x,int t){
Top[x]=t;
ct++;
dfn[x]=ct;
ref[ct]=x;
if(h[x]!=0){
ReDFS(h[x],t);
for(int T:G[x]){
if(T!=h[x]){
ReDFS(T,T);
}
}
}
}
I void PutDown(int p){
Lazy[p<<1]+=Lazy[p];
Lazy[p<<1|1]+=Lazy[p];
Lazy[p].MaxV=Lazy[p].SumD=0;
}
I void Modify(int p,int lf,int rt,const int l,const int r,const Item A){
if(l<=lf&&rt<=r){
Lazy[p]+=A;
}else{
PutDown(p);
int mid=lf+rt>>1;
if(l<=mid){
Modify(p<<1,lf,mid,l,r,A);
}
if(r>mid){
Modify(p<<1|1,mid+1,rt,l,r,A);
}
}
}
I void Release(int p,int lf,int rt){
if(lf==rt){
Item A;
A.SumD=A.MaxV=w[ref[rt]];
A+=Lazy[p];
opt[ref[lf]]=A;
}else{
PutDown(p);
Release(p<<1,lf,lf+rt>>1);
Release(p<<1|1,lf+rt+2>>1,rt);
}
}
int main(){
int n;
scanf("%*d%d",&n);
for(int i=2;i<=n;i++){
scanf("%d",f+i);
G[f[i]].push_back(i);
}
for(R i=1;i<=n;i++){
scanf("%d",w+i);
if(i!=1){
sum[f[i]]+=w[i];
}
End[i]=i;
}
priority_queue<Node>Q;
DS.Init(n);
for(R i=1;i<=n;i++){
opt[i].MaxV=sum[i];
opt[i].SumD=sum[i]-w[i];
Q.push(Pair(i,1,sum[i]-w[i],sum[i]));
}
while(Q.empty()==false){
int x=Q.top().Id,s=Q.top().Size;
Q.pop();
if(DS.GetSize(x)==s&&x!=1){
Node Tem;
int y=DS.GetF(f[x]);
opt[y]+=opt[x];
Tem.Mod=opt[y];
Tem.Id=y;
DS.Merge(y,x);
Tem.Size=DS.GetSize(y);
Next[End[y]]=x;
End[y]=End[x];
Q.push(Tem);
}
}
PreDFS(1);
ReDFS(1,1);
int cur=1,t;
while(cur!=0){
t=cur;
Item Tem;
Tem.MaxV=sum[cur];
Tem.SumD=sum[cur]-w[cur];
while(t!=0){
Modify(1,1,n,dfn[Top[t]],dfn[t],Tem);
t=f[Top[t]];
}
cur=Next[cur];
}
Release(1,1,n);
for(R i=1;i<=n;i++){
printf("%lld ",opt[i].MaxV);
}
return 0;
}
用LCT可以做到时间复杂度 O ( n log 2 n ) O(n \log_2n) O(nlog2n)。
答案不好直接算,但枚举连通块后可以用线性基算出只要求联通块之间不连通的方案数。若联通块个数为 B B B,则容斥系数为 ( − 1 ) B − 1 ( B − 1 ) ! (-1)^{B-1}(B-1)! (−1)B−1(B−1)!。
时间复杂度 O ( B e l l n s ) O(Bell_n s) O(Bellns),空间复杂度 O ( n 2 s ) O(n^2 s) O(n2s)
#include
#include
#define R register int
#define L long long
#define I inline
char G[60][46];
L Edge[60];
int id[10][10],bel[10],fac[10];
class Basis{
L Vec[45];
int Tot;
public:
I void Init(){
Tot=0;
}
I bool Insert(L x){
for(R i=0;i!=Tot;i++){
if(x>(x^Vec[i])){
x^=Vec[i];
}
}
if(x!=0){
Vec[Tot]=x;
Tot++;
return true;
}
return false;
}
};
I void DFS(int t,int sz,L cur,const int n,const int m,L&ans){
if(t==n){
Basis B;
B.Init();
L pw=fac[sz-1];
for(R i=0;i!=m;i++){
if(B.Insert(Edge[i]&cur)==false){
pw<<=1;
}
}
if((sz&1)==1){
ans+=pw;
}else{
ans-=pw;
}
}else{
for(R i=0;i!=sz;i++){
L tem=cur;
for(R j=0;j!=t;j++){
if(bel[j]!=i){
tem|=1ll<<id[t][j];
}
}
bel[t]=i;
DFS(t+1,sz,tem,n,m,ans);
}
for(R i=0;i!=t;i++){
cur|=1ll<<id[t][i];
}
bel[t]=sz;
DFS(t+1,sz+1,cur,n,m,ans);
}
}
int main(){
fac[0]=1;
int s,m,n=2,ct=0;
scanf("%d",&s);
for(R i=0;i!=s;i++){
scanf("%s",G[i]);
}
m=strlen(G[0]);
while(n*(n-1)!=m<<1){
n++;
}
for(R i=0;i!=n;i++){
for(R j=i+1;j!=n;j++){
id[i][j]=id[j][i]=ct;
ct++;
}
}
for(R i=1;i!=n;i++){
fac[i]=fac[i-1]*i;
}
for(R i=0;i!=s;i++){
for(R j=m-1;j!=-1;j--){
Edge[i]=Edge[i]<<1|G[i][j]^'0';
}
}
L ans=0;
DFS(0,0,0,n,s,ans);
printf("%lld",ans);
return 0;
}
维护正整数的可重集合,支持插入、删除操作,并且每次操作后都要查询子集异或和的最大值。输入的数要异或上次询问的答案。
1 ⩽ n ⩽ 500000 1 \leqslant n \leqslant 500000 1⩽n⩽500000
时间限制6s,空间限制512MB。
原版shallot
和原版类似地,维护时间戳线性基。由于解密方式为逐位异或,因此也可以逐位解密。根据之前的答案可以得到当前操作的数的前缀。一个删除操作可能当前位是一,可能是零,可以删掉两种可能的数,然后再加入另一种数。其他的过程和原版类似。
时间复杂度 O ( n log 2 2 A ) O(n \log_2^2 A) O(nlog22A),空间复杂度 O ( n ) O(n) O(n)
#include
#include
using namespace std;
#define R register int
#define I inline
#define N 500001
I void Swap(int&x,int&y){
int t=x;
x=y;
y=t;
}
int Next[N],Time[N],a[N],opt[N],ans[N];
class Basis{
int Vec[31],Time[31],Tot;
public:
I void Clear(){
Tot=0;
}
I void Insert(int x,int t){
int cur=0,d=30;
while(cur!=Tot&&d!=-1){
if((x>>d&1)==0&&(Vec[cur]>>d&1)==1){
cur++;
}else if((x>>d&1)==1){
if(t>Time[cur]||(Vec[cur]>>d&1)==0){
Swap(t,Time[cur]);
Swap(Vec[cur],x);
}
if((x>>d&1)==1){
x^=Vec[cur];
}
cur++;
}
d--;
}
if(x!=0){
Vec[Tot]=x;
Time[Tot]=t;
Tot++;
}
}
I int GetAns(int lim){
int res=0;
for(R i=0;i!=Tot;i++){
if(Time[i]>lim&&res<(res^Vec[i])){
res^=Vec[i];
}
}
return res;
}
};
int main(){
int n,pre=0,x,cur;
scanf("%d",&n);
for(R i=1;i<=n;i++){
scanf("%d%d",opt+i,a+i);
}
Basis B;
for(R i=31;i!=-1;i--){
B.Clear();
unordered_map<int,int>Last;
for(R j=n;j!=0;j--){
x=(a[j]^ans[j-1])⪯
if(Last.count(x)==0){
Next[j]=N;
}else{
Next[j]=Last[x];
}
if(opt[j]==2){
Last[x]=j;
}
}
unordered_map<int,int>C;
cur=0;
for(R j=1;j<=n;j++){
x=(a[j]^ans[j-1])&pre|(a[j]^cur)&1<<i;
if(opt[j]==1){
B.Insert(x,Next[j]);
C[x]++;
}else{
if(C.count(x^1<<i)!=0){
B.Insert(x^1<<i,Next[j]);
}
if(C[x]==1){
C.erase(x);
}else{
C[x]--;
B.Insert(x,Next[j]);
}
}
cur=B.GetAns(j);
ans[j]|=cur&1<<i;
}
pre|=1<<i;
}
for(R i=1;i<=n;i++){
printf("%d\n",ans[i]);
}
return 0;
}
类似点分树,按编号从小到大分治建树,这里用并查集并从大到小枚举点即可。同样的可以建出编号从大到小的树。
要求的数对,满足在两棵树上均为祖先后代关系,因此在一棵树上DFS然后在另一棵树DFS序上统计答案即可。
#include
#include
using namespace std;
#define R register int
#define I inline
#define N 1048577
class FastReader{
char Text[4194304];
int Len,Cur;
I void Read(){
Cur=0;
Len=fread(Text,1,4194304,stdin);
}
I char GetChar(){
if(Cur==Len){
Read();
}
if(Len==0){
return 0;
}
char res=Text[Cur];
Cur++;
return res;
}
public:
I int ReadInt(){
int res=0;
char c=GetChar();
while(c<'0'||c>'9'){
c=GetChar();
}
while(c>='0'&&c<='9'){
res=res*10+(c^'0');
c=GetChar();
}
return res;
}
}Reader;
vector<int>G[N];
int Last[2][N],Next[2][N];
class DisjointSet{
int F[N];
I int GetF(int x){
if(x==F[x]){
return x;
}
F[x]=GetF(F[x]);
return F[x];
}
public:
I void Init(const int n){
for(R i=1;i<=n;i++){
F[i]=i;
}
}
I void Merge(int x,int y,const int t){
x=GetF(x);
Next[t][x]=Last[t][y];
Last[t][y]=x;
F[x]=y;
}
}S;
int C[N],in[N],out[N],ct;
I void PreDFS(int x){
ct++;
in[x]=ct;
for(R i=Last[1][x];i!=0;i=Next[1][i]){
PreDFS(i);
}
out[x]=ct;
}
I void Add(int x,const int d){
for(R i=x;i<N;i+=i&-i){
C[i]+=d;
}
}
I int GetSum(int x){
int res=0;
for(R i=x;i!=0;i&=i-1){
res+=C[i];
}
return res;
}
I void ReDFS(int x,long long&ans){
ans+=GetSum(out[x])-GetSum(in[x]-1);
Add(in[x],1);
for(R i=Last[0][x];i!=0;i=Next[0][i]){
ReDFS(i,ans);
}
Add(in[x],-1);
}
int main(){
int n=Reader.ReadInt(),x,y;
for(R i=1;i!=n;i++){
x=Reader.ReadInt();
y=Reader.ReadInt();
G[x].push_back(y);
G[y].push_back(x);
}
S.Init(n);
for(R i=n;i!=0;i--){
for(int T:G[i]){
if(T>i){
S.Merge(T,i,0);
}
}
}
S.Init(n);
for(R i=1;i<=n;i++){
for(int T:G[i]){
if(T<i){
S.Merge(T,i,1);
}
}
}
long long ans=0;
PreDFS(n);
ReDFS(1,ans);
printf("%lld",ans);
return 0;
}
首先一个数列只可能删开头两个和末尾两数。当数列还剩三个数的时候可以随便删。设 f i f_{i} fi 表示删除 i i i 之前的数,保留 i i i 以及之后的数的方案数。若 f i f_i fi 要转移到 f j f_j fj,则要先删除 i , j i,j i,j 之间的数,最后删除 i i i。可以用单调栈求出 j j j 的范围。从左到右和从右到左两次DP即可枚举最后三个数中间的数统计答案。
时空复杂度 O ( n ) O(n) O(n)
#include
#include
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 200001
#define P 1000000007
I void Add(int&x,const int y){
x+=y;
if(x>=P){
x-=P;
}
}
I int GetInv(int x){
int res=1,y=P-2;
while(y!=0){
if((y&1)==1){
res=(L)res*x%P;
}
y>>=1;
x=(L)x*x%P;
}
return res;
}
int a[N],f[N],g[N],rt[N],odd[N],even[N],fac[N],invf[N];
I void DP(const int n){
if(a[1]>a[2]){
for(R i=1;i<=n;i++){
a[i]=-a[i];
}
}
int T1=0,T0=0;
for(int i=n;i!=0;i--){
int l=1,r=T0,ans=n,mid;
while(l<=r){
mid=l+r>>1;
if(a[even[mid]]<a[i]){
ans=even[mid];
l=mid+1;
}else{
r=mid-1;
}
}
l=1;
r=T1;
while(l<=r){
mid=l+r>>1;
if(a[odd[mid]]>a[i]){
if(odd[mid]<ans){
ans=odd[mid];
}
l=mid+1;
}else{
r=mid-1;
}
}
rt[i]=ans;
if((i&1)==1){
while(T1!=0&&a[odd[T1]]<a[i]){
T1--;
}
T1++;
odd[T1]=i;
}else{
while(T0!=0&&a[even[T0]]>a[i]){
T0--;
}
T0++;
even[T0]=i;
}
f[i]=0;
}
Add(f[2],1);
Add(f[rt[1]],P-1);
for(R i=2;i!=n;i++){
Add(f[i],f[i-1]);
Add(f[i+1],f[i]);
Add(f[rt[i]],P-f[i]);
}
}
I int GetC(int n,int m){
return(L)fac[n]*invf[m]%P*invf[n-m]%P;
}
I void Solve(){
int n,ans=0;
scanf("%d",&n);
for(R i=1;i<=n;i++){
scanf("%d",a+i);
}
DP(n);
for(R i=1;i<=n;i++){
g[i]=f[n-i+1];
}
reverse(a+1,a+n+1);
DP(n);
for(R i=2;i!=n;i++){
ans=(6ll*f[i]*g[i]%P*GetC(n-3,i-2)+ans)%P;
}
printf("%d\n",ans);
}
int main(){
fac[0]=1;
for(R i=1;i!=N;i++){
fac[i]=(L)fac[i-1]*i%P;
}
invf[N-1]=GetInv(fac[N-1]);
for(R i=N-1;i!=0;i--){
invf[i-1]=(L)invf[i]*i%P;
}
int t;
scanf("%d",&t);
for(R i=0;i!=t;i++){
Solve();
}
return 0;
}
给定 N , P N,P N,P,求两个问题对 P P P 取模的答案:
1 ⩽ N ⩽ 1 0 8 , 1 ⩽ P ⩽ 1000000009 1 \leqslant N \leqslant 10^8,1 \leqslant P \leqslant 1000000009 1⩽N⩽108,1⩽P⩽1000000009
时间限制2s,空间限制512MB。
对于第二个问题,根据题意答案为:
∑ A = 1 N ∑ B = 1 N ∑ C = 1 N ∑ D = 1 N [ A C ⩽ N ] [ B D ⩽ N ] [ gcd ( A , B ) = 1 ] [ gcd ( C , D ) = 1 ] \sum_{A=1}^N \sum_{B=1}^N \sum_{C=1}^N \sum_{D=1}^N[AC \leqslant N][BD \leqslant N][\gcd(A,B)=1][\gcd(C,D)=1] ∑A=1N∑B=1N∑C=1N∑D=1N[AC⩽N][BD⩽N][gcd(A,B)=1][gcd(C,D)=1]
根据莫比乌斯反演:
∑ d 1 = 1 N ∑ d 2 = 1 N μ ( d 1 ) μ ( d 2 ) ∑ A = 1 N ∑ B = 1 N ∑ C = 1 N ∑ D = 1 N [ A C d 1 d 2 ⩽ N ] [ B D d 1 d 2 ⩽ N ] \sum_{d_1=1}^N \sum_{d_2=1}^N \mu(d_1) \mu(d_2) \sum_{A=1}^N \sum_{B=1}^N \sum_{C=1}^N \sum_{D=1}^N[ACd_1d_2 \leqslant N][BDd_1d_2 \leqslant N] ∑d1=1N∑d2=1Nμ(d1)μ(d2)∑A=1N∑B=1N∑C=1N∑D=1N[ACd1d2⩽N][BDd1d2⩽N]
枚举 d 1 d 2 d_1d_2 d1d2 得到:
∑ T = 1 N ∑ d ∣ T μ ( d ) μ ( T d ) ∑ A = 1 N ∑ B = 1 N ∑ C = 1 N ∑ D = 1 N [ A C T ⩽ N ] [ B D T ⩽ N ] \sum_{T=1}^N \sum_{d|T} \mu(d)\mu(\frac{T}d)\sum_{A=1}^N \sum_{B=1}^N \sum_{C=1}^N \sum_{D=1}^N[ACT \leqslant N][BDT \leqslant N] ∑T=1N∑d∣Tμ(d)μ(dT)∑A=1N∑B=1N∑C=1N∑D=1N[ACT⩽N][BDT⩽N]
设 f ( n ) = ∑ d ∣ n μ ( d ) μ ( n d ) , g ( n ) = ∑ i = 1 n ∑ j = 1 n [ i j ⩽ n ] f(n)=\sum_{d|n}\mu(d)\mu(\frac{n}d),g(n)=\sum_{i=1}^n \sum_{j=1}^n [ij \leqslant n] f(n)=∑d∣nμ(d)μ(dn),g(n)=∑i=1n∑j=1n[ij⩽n],则式子变为:
∑ T = 1 N f ( T ) g ( N T ) 2 \sum_{T=1}^N f(T) g(\frac{N}T)^2 ∑T=1Nf(T)g(TN)2
整除分块求 f , g f,g f,g 和答案即可。
对于第一个问题,类似地答案为:
∑ d 1 = 1 N ∑ d 2 = 1 N μ ( d 1 ) μ ( d 2 ) ∑ A , C [ A C ⩽ [ N d 1 d 2 ] ] ∑ B , D ∑ E = 1 N ∑ F = 1 N [ B D E ⩽ [ N d 1 d 2 ] ] [ B D F ⩽ [ N d 1 d 2 ] ] [ gcd ( E , F ) = 1 ] \sum_{d_1=1}^N \sum_{d_2=1}^N \mu(d_1)\mu(d_2) \sum_{A,C}[AC \leqslant [\frac{N}{d_1d_2}]] \sum_{B,D} \sum_{E=1}^N \sum_{F=1}^N[BDE \leqslant [\frac{N}{d_1d_2}]][BDF \leqslant [\frac{N}{d_1d_2}]][\gcd(E,F)=1] ∑d1=1N∑d2=1Nμ(d1)μ(d2)∑A,C[AC⩽[d1d2N]]∑B,D∑E=1N∑F=1N[BDE⩽[d1d2N]][BDF⩽[d1d2N]][gcd(E,F)=1]
容易发现 E , F E,F E,F 上界相同,改写式子:
∑ T = 1 N f ( T ) g ( [ N T ] ) ∑ B , D ( 2 ∑ i = 1 [ N B D T ] φ ( i ) − 1 ) \sum_{T=1}^N f(T) g([\frac{N}T]) \sum_{B,D}(2 \sum_{i=1}^{[\frac{N}{BDT}]}\varphi(i)-1) ∑T=1Nf(T)g([TN])∑B,D(2∑i=1[BDTN]φ(i)−1)
设 h ( n ) = ∑ i = 1 n ∑ j = 1 n ( 2 ∑ k = 1 [ n i j ] φ ( k ) − 1 ) h(n)=\sum_{i=1}^n \sum_{j=1}^n(2 \sum_{k=1}^{[\frac{n}{ij}]}\varphi(k)-1) h(n)=∑i=1n∑j=1n(2∑k=1[ijn]φ(k)−1),式子化简为:
∑ T = 1 N f ( T ) g ( N T ) h ( N T ) \sum_{T=1}^N f(T) g(\frac{N}T)h(\frac{N}T) ∑T=1Nf(T)g(TN)h(TN)
用杜教筛求 f f f,整除分块求 g , h g,h g,h 和答案即可。函数 f , g , h f,g,h f,g,h 均可预处理较小的定义域。
时间复杂度 O ( N 3 4 ) O(N^{\frac{3}4}) O(N43),空间复杂度 O ( N 2 3 ) O(N^{\frac{2}3}) O(N32)
#include
#include
using namespace std;
#define R register int
#define I inline
#define L long long
#define N 999999
bool vis[N];
int prime[78498],mu[N],f[N],phi[N],h[N],g[N];
I int GetISum(int n,const int P){
if(n<N){
return g[n];
}
int r,t,res=0;
for(R i=1;i<=n;i=r+1){
t=n/i;
r=n/t;
res=((1ll+r-i)*t+res)%P;
}
return res;
}
unordered_map<int,int>Qm,Qf,Qp;
I int GetMuSum(int n){
if(n<N){
return mu[n];
}
if(Qm.count(n)!=0){
return Qm[n];
}
int res=1,r,t,pre=0,cur;
for(R i=1;i<<1<=n;i=r+1){
t=n/i;
r=n/t;
cur=GetMuSum(r);
res-=(cur-pre)*(t-1);
pre=cur;
}
Qm[n]=res;
return res;
}
I int GetFSum(int n,const int P){
if(n<N){
return f[n];
}
if(Qf.count(n)!=0){
return Qf[n];
}
int res=GetMuSum(n),r,t,pre=0,cur;
for(R i=1;i<<1<=n;i=r+1){
t=n/i;
r=n/t;
cur=GetFSum(r,P);
res=(res-(t-1ll)*(cur-pre))%P;
pre=cur;
}
Qf[n]=res;
return res;
}
I int GetPhiSum(int n,const int P){
if(n<N){
return phi[n];
}
if(Qp.count(n)!=0){
return Qp[n];
}
int res=((1ll+n)*n>>1)%P,t,r,pre=0,cur;
for(R i=1;i<<1<=n;i=r+1){
t=n/i;
r=n/t;
cur=GetPhiSum(r,P);
res=(res-(t-1ll)*(cur-pre))%P;
pre=cur;
}
Qp[n]=res;
return res;
}
I int GetHSum(int n,const int P){
if(n<N){
return h[n];
}
int res=0,t,r,cur,pre=0;
for(R i=1;i<=n;i=r+1){
t=n/i;
r=n/t;
cur=GetISum(r,P);
res=((2ll*GetPhiSum(t,P)-1)*(cur-pre)+res)%P;
pre=cur;
}
return res;
}
int main(){
mu[1]=phi[1]=1;
int type,n=0,P,r,ans=0,t,pre=0,cur;
for(R i=2;i!=N;i++){
if(vis[i]==false){
prime[n]=i;
n++;
mu[i]=-1;
phi[i]=i-1;
}
for(R j=0;prime[j]*i<N;j++){
vis[i*prime[j]]=true;
if(i%prime[j]==0){
mu[i*prime[j]]=0;
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
mu[i*prime[j]]=-mu[i];
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
for(R i=1;i!=N;i++){
for(R j=1;j*i<N;j++){
if(mu[j]!=0){
f[i*j]+=mu[i]*mu[j];
}
g[i*j]++;
}
}
scanf("%d%d%d",&type,&n,&P);
for(R i=1;i!=N;i++){
t=i==1?1:phi[i]<<1;
for(R j=1;j*i<N;j++){
int&d=h[i*j];
d=((L)t*g[j]+d)%P;
}
}
for(R i=2;i!=N;i++){
f[i]+=f[i-1];
phi[i]+=phi[i-1];
if(phi[i]>=P){
phi[i]-=P;
}
g[i]+=g[i-1];
if(g[i]>=P){
g[i]-=P;
}
h[i]+=h[i-1];
if(h[i]>=P){
h[i]-=P;
}
mu[i]+=mu[i-1];
}
if(type==2){
for(R i=1;i<=n;i=r+1){
t=n/i;
r=n/t;
cur=GetFSum(r,P);
t=GetISum(t,P);
ans=((L)(cur-pre)*t%P*t+ans)%P;
pre=cur;
}
}else{
for(R i=1;i<=n;i=r+1){
t=n/i;
r=n/t;
cur=GetFSum(r,P);
ans=((L)(cur-pre)*GetISum(t,P)%P*GetHSum(t,P)+ans)%P;
pre=cur;
}
}
printf("%d",ans<0?ans+P:ans);
return 0;
}
给定一个长度为 n n n 的序列值域为 [ 1 , m ] [1,m] [1,m],经过排列 p p p 映射后给出 q q q 次询问区间中最长子序列的值从左到右一次减少一。询问强制在线。
1 ⩽ n , m ⩽ 300000 1 \leqslant n,m \leqslant 300000 1⩽n,m⩽300000
1 ⩽ q ⩽ 1 0 6 1 \leqslant q \leqslant 10^6 1⩽q⩽106
时间限制10s,空间限制1GB。
先按题目要求进行映射,然后对序列进行分块。预处理 f i , j f_{i,j} fi,j 表示从第 i i i 块开头到以 j j j 结尾的最长序列长度或从第 i − 1 i-1 i−1 块结尾到以 j j j 开头的最长长度。 g i , j g_{i,j} gi,j 表示从第 i i i 块开头到第 j j j 块结尾的答案。
对于每次询问,如果左右端点在同一块内则直接暴力DP即可。否则先在 g g g 中找到整块之间的答案,枚举散块部分,统计出整块到散块的答案。枚举右端散块时根据 f f f 值修改暴力DP的初值,在枚举左端散块时可以统计合并后的答案。
时间复杂度 O ( ( n + q ) n ) O((n+q) \sqrt n) O((n+q)n),空间复杂度 O ( n n ) O(n \sqrt n) O(nn)
#include
#include
using namespace std;
#define R register int
#define N 300002
inline void Max(int&x,const int y){
if(x<y){
x=y;
}
}
int a[N],p[N],dp[720][N],seg[720][720],f[N];
int main(){
const int BLOCK=417;
int n,m,q,type,ans=0,l,r,bl,br;
scanf("%d%d%d%d",&n,&m,&q,&type);
for(R i=1;i<=n;i++){
scanf("%d",a+i);
}
for(R i=1;i<=m;i++){
scanf("%d",&r);
p[r]=i;
}
for(R i=1;i<=n;i++){
a[i]=p[a[i]];
}
for(R i=1;i*BLOCK<=n;i++){
for(R j=i*BLOCK;j<=n;j++){
dp[i][j]=f[a[j]]=f[a[j]+1]+1;
}
for(R j=1;j<=m;j++){
f[j]=0;
}
bl=i;
br=0;
for(R j=i*BLOCK;j<=n;j++){
Max(seg[i][bl],dp[i][j]);
br++;
if(br==BLOCK){
br=0;
bl++;
}
}
for(R j=i+1;j*BLOCK<=n;j++){
Max(seg[i][j],seg[i][j-1]);
}
for(R j=i*BLOCK-1;j!=0;j--){
dp[i][j]=f[a[j]]=f[a[j]-1]+1;
}
for(R j=1;j<=m;j++){
f[j]=0;
}
}
for(R i=0;i!=q;i++){
scanf("%d%d",&l,&r);
if(type==1){
l^=ans;
r^=ans;
}
bl=l/BLOCK;
br=r/BLOCK;
ans=1;
if(bl==br){
for(R j=l;j<=r;j++){
int&g=f[a[j]];
g=f[a[j]+1]+1;
Max(ans,g);
}
for(R j=l;j<=r;j++){
f[a[j]]=0;
}
}else{
if(bl!=br-1){
ans=seg[bl+1][br-1];
}
for(R j=br*BLOCK;j<=r;j++){
Max(ans,dp[bl+1][j]);
Max(f[dp[bl+1][j]+a[j]-1],dp[bl+1][j]);
}
for(R j=(bl+1)*BLOCK-1;j>=l;j--){
Max(ans,dp[br][j]);
Max(f[a[j]],f[a[j]-1]+1);
Max(ans,f[a[j]]);
}
for(R j=br*BLOCK;j<=r;j++){
f[dp[bl+1][j]+a[j]-1]=0;
}
for(R j=(bl+1)*BLOCK-1;j>=l;j--){
f[a[j]]=0;
}
}
printf("%d\n",ans);
}
return 0;
}
首先判断当初状态和末状态完全相同时有解。如果不同且末状态的每条边两端的颜色都不同或初状态所有点颜色相同无解。
当树是一条链时可以简单判断出答案。否则可以通过归纳证明一定有解。
时间复杂度 O ( ∑ n ) O(\sum n) O(∑n),空间复杂度 O ( n ) O(n) O(n)
#include
#include
using namespace std;
#define R register int
#define I inline
#define N 100001
char s[N];
int col[2][N];
I void Read(int*A,const int n){
scanf("%s",s+1);
for(R i=1;i<=n;i++){
A[i]=s[i]^'0';
}
}
vector<int>G[N];
I void Solve(){
int n,x,y,s0,s1,l0,l1;
scanf("%d",&n);
bool tagZ=true,tagA=true,tagX=true,tagT=true;
Read(col[0],n);
Read(col[1],n);
for(R i=1;i<=n;i++){
vector<int>().swap(G[i]);
if(col[0][i]!=col[1][i]){
tagZ=false;
}
if(col[0][i]!=col[0][1]){
tagA=false;
}
}
for(R i=1;i!=n;i++){
scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
if(col[1][x]==col[1][y]){
tagX=false;
}
}
if(tagZ==true){
puts("TAK");
return;
}
if(tagA==true||tagX==true){
puts("NIE");
return;
}
for(R i=1;i<=n;i++){
if(G[i].size()>2){
tagT=false;
break;
}
if(G[i].size()==1){
x=i;
}
}
if(tagT==true){
s0=col[0][x];
s1=col[1][x];
int F=0;
l0=l1=1;
while(true){
if(G[x][0]!=F){
y=G[x][0];
}else if(G[x].size()==1){
break;
}else{
y=G[x][1];
}
if(col[0][y]!=col[0][x]){
l0++;
}
if(col[1][y]!=col[1][x]){
l1++;
}
F=x;
x=y;
}
if(l0==l1&&s0==s1||l0>l1){
puts("TAK");
}else{
puts("NIE");
}
}else{
puts("TAK");
}
}
int main(){
int t;
scanf("%d",&t);
for(R i=0;i!=t;i++){
Solve();
}
return 0;
}
将每个位置抽象成点,仿照暴力DP的过程, i i i 向 i + 1 i+1 i+1 连一条长度为零的边, i i i 向 i + k i+k i+k 连边,长度为区间和。建出的图可以看做每列 k k k 个点的网格图。
对网格图进行分治。若横向长度更长,则枚举中间列的所有点,遍历整个图然后枚举跨过中间列的询问更新答案然后分治左右部分。若横向长度更长,则枚举中间行的所有点,如果这是第一次竖向分治则还要额外枚举顶部行的所有点。
时间复杂度 O ( ( n log 2 n + q ) n ) O((n \log_2 n+q) \sqrt n) O((nlog2n+q)n),空间复杂度 O ( n + q ) O(n+q) O(n+q)
#include
#include
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 300001
#define M 600000
I void Max(L&x,const L y){
if(x<y){
x=y;
}
}
struct Query{
int Start,End,Id;
I void Read(int d){
scanf("%d%d",&Start,&End);
Id=d;
Start--;
}
};
int a[M];
L ans[N],sum[M],f[M];
I void DP(int lx,int rx,int ly,int ry,const int cx,const int cy,const bool tag,const int m){
int cur=cx*m+cy;
for(R i=ly;i<=ry;i++){
f[cx*m+i]=0;
}
for(R i=cx-1;i>=lx;i--){
for(R j=ry;j>=ly;j--){
int x=i*m+j;
if(tag==false&&j>cy){
f[x]=0;
}else{
f[x]=tag==true||j!=cy?f[x+1]:0;
if(x+m<=cur){
Max(f[x],f[x+m]+sum[x+m]-sum[x]);
}
}
}
}
for(R i=cx+1;i<=rx;i++){
for(R j=ly;j<=ry;j++){
int x=i*m+j;
if(tag==false&&j<cy){
f[x]=0;
}else{
f[x]=tag==true||j!=cy?f[x-1]:0;
if(x-m>=cur){
Max(f[x],f[x-m]+sum[x]-sum[x-m]);
}
}
}
}
}
I void Solve(int lx,int rx,int ly,int ry,bool tag,vector<Query>&Q,const int m){
if(lx==rx&&ry==ly||Q.empty()==true){
return;
}
vector<Query>Ql,Qr,Qm;
if(ry-ly<rx-lx){
int mid=lx+rx>>1;
for(auto T:Q){
int a=T.Start/m,b=T.End/m;
if(a>mid){
Qr.push_back(T);
}else if(b>mid){
Qm.push_back(T);
}else{
Ql.push_back(T);
}
}
for(R i=ly;i<=ry;i++){
int cur=mid*m+i;
DP(lx,rx,ly,ry,mid,i,tag,m);
for(auto T:Qm){
if(T.Start<=cur&&T.End>=cur){
Max(ans[T.Id],f[T.Start]+f[T.End]);
}
}
}
Solve(lx,mid,ly,ry,tag,Ql,m);
Solve(mid+1,rx,ly,ry,tag,Qr,m);
}else{
if(tag==true){
for(R i=lx;i!=rx;i++){
int x=i*m+ry;
DP(lx,rx,ly,ry,i,ry,true,m);
for(auto T:Q){
if(T.Start<=x&&T.End>=x){
Max(ans[T.Id],f[T.Start]+f[T.End]);
}
}
}
}
int mid=ly+ry>>1;
for(auto T:Q){
int a=T.Start%m,b=T.End%m;
if(a>mid&&b>mid){
Qr.push_back(T);
}else if(a<=mid&&b<=mid){
Ql.push_back(T);
}else if(a<b){
Qm.push_back(T);
}
}
for(R i=lx;i<=rx;i++){
int cur=i*m+mid;
DP(lx,rx,ly,ry,i,mid,tag,m);
for(auto T:Qm){
if(T.Start<=cur&&T.End>=cur){
Max(ans[T.Id],f[T.Start]+f[T.End]);
}
}
}
Solve(lx,rx,ly,mid,false,Ql,m);
Solve(lx,rx,mid+1,ry,false,Qr,m);
}
}
int main(){
int n,k,q,b;
scanf("%d%d%d",&n,&k,&q);
for(R i=1;i<=n;i++){
scanf("%d",a+i);
}
b=n%k;
if(b!=k-1){
n+=k-1-b;
}
for(R i=1;i<=n;i++){
if(i!=0){
sum[i]=sum[i-1]+a[i];
}
}
vector<Query>Q;
for(R i=0;i!=q;i++){
Query G;
G.Read(i);
Q.push_back(G);
}
Solve(0,(n-k+1)/k,0,k-1,true,Q,k);
for(R i=0;i!=q;i++){
printf("%lld\n",ans[i]);
}
return 0;
}
有一个简单的网络流暴力,多次求最小割。由于图的特殊性,可以用特殊的方式求解。给 k k k 个源点随机一个 k k k 维向量,令其他点的向量为连向该点的向量随机线性组合的结果。最后以一段区间的最小割即为区间内的线性基大小。用时间戳线性基求解即可。
时间复杂度 O ( n k 2 + m k ) O(nk^2+mk) O(nk2+mk),空间复杂度 O ( n k + m + k 2 ) O(nk+m+k^2) O(nk+m+k2)
#include
#include
#include
#include
#include
#include
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 100001
#define P 1000000009
typedef int Vectors[50];
I void Swap(int&x,int&y){
int t=x;
x=y;
y=t;
}
I int GetInv(int x){
int y=P-2,res=1;
while(y!=0){
if((y&1)==1){
res=(L)res*x%P;
}
x=(L)x*x%P;
y>>=1;
}
return res;
}
vector<int>G[N];
Vectors val[N];
int deg[N],ord[N];
I void VectorAdd(Vectors A,Vectors B,int k,const int n){
for(R i=0;i!=n;i++){
A[i]=((L)B[i]*k+A[i])%P;
}
}
class Basis{
Vectors Vec[50];
int Time[50],Tot=0;
public:
I void Insert(Vectors A,int t,const int n){
int cur=0,d=0;
while(cur!=Tot&&d!=n){
if(A[d]==0&&Vec[cur][d]==0){
d++;
}else if(A[d]==0){
cur++;
d++;
}else{
if(t>Time[cur]||Vec[cur][d]==0){
Swap(Time[cur],t);
for(R i=d;i!=n;i++){
Swap(Vec[cur][i],A[i]);
}
}
if(A[d]!=0){
int f=(L)GetInv(Vec[cur][d])*(P-A[d])%P;
for(R i=d;i!=n;i++){
A[i]=((L)f*Vec[cur][i]+A[i])%P;
}
}
cur++;
d++;
}
}
bool tag=false;
for(R i=0;i!=n;i++){
if(A[i]!=0){
tag=true;
break;
}
}
if(tag==true){
for(R i=0;i!=n;i++){
Vec[Tot][i]=A[i];
}
Time[Tot]=t;
Tot++;
}
}
I void Copy(vector<int>&A){
for(R i=0;i!=Tot;i++){
A.push_back(Time[i]);
}
sort(A.begin(),A.end());
}
}B;
long long ans[51];
int main(){
int n,m,k,x,y,ct;
scanf("%d%d%d",&n,&m,&k);
for(R i=0;i!=m;i++){
scanf("%d%d",&x,&y);
G[x].push_back(y);
if(x>k){
deg[y]++;
}
}
mt19937 Rand(time(0));
uniform_int_distribution<int>Pool(0,P-1);
for(R i=1;i<=k;i++){
for(R j=0;j!=k;j++){
val[i][j]=Pool(Rand);
}
}
stack<int>S;
for(int i=n;i!=k;i--){
if(deg[i]==0){
S.push(i);
}
}
for(R i=1;i<=k;i++){
ord[i]=i;
}
ct=k;
while(S.empty()==false){
x=S.top();
S.pop();
ct++;
ord[ct]=x;
for(int T:G[x]){
deg[T]--;
if(deg[T]==0){
S.push(T);
}
}
}
for(R i=1;i<=n;i++){
x=ord[i];
for(int T:G[x]){
VectorAdd(val[T],val[x],Pool(Rand),k);
}
}
for(R i=k+1;i<=n;i++){
B.Insert(val[i],i,k);
vector<int>A(1,k);
B.Copy(A);
int sz=A.size();
for(R j=sz-1;j!=0;j--){
ans[sz-j]+=A[j]-A[j-1];
}
ans[0]+=i-A.back();
}
for(R i=0;i<=k;i++){
printf("%lld\n",ans[i]);
}
return 0;
}