题目:
WC2020 猜数游戏
WC2020 选课
线性规划问题
操作
Count Number of Sequences
Tree and Brilliant Array
WF2014 Bagage
Hotel加强版
Arietta
Poborcy podatkowi
WC2020 猜数游戏
如果猜数
x
x
x 可以得到
y
y
y,则从
x
x
x 连一条有向边到
y
y
y。如果事先建好了图,则可以简单算出每个数对答案做出贡献的概率。由于题目的特殊性质,模数一定有原根,将数按照阶分类,每一类中的数可以相互到达,类之间为一个有向无环图。因此算出每个数的阶然后分类即可。
时间复杂度
O
(
n
P
)
O(n \sqrt P)
O(nP),空间复杂度
O
(
n
+
P
)
O(n+\sqrt P)
O(n+P)
#include<iostream>
#include<vector>
#include<unordered_map>
#include<unordered_set>
#include<algorithm>
using namespace std;
#define R register int
#define L long long
#define I inline
#define P 998244353
int a[5000],d[5000],pw[5000];
vector<int>G[5000];
bool vis[5000];
I int PowMod(int x,int y,const int MOD){
int res=1;
while(y!=0){
if((y&1)==1){
res=(L)res*x%MOD;
}
x=(L)x*x%MOD;
y>>=1;
}
return res;
}
I int GetP(int n){
for(R i=3;i*i<=n;i++){
if(n%i==0){
return i;
}
}
return n;
}
I void DFS(int x){
if(vis[x]==false){
vis[x]=true;
for(int T:G[x]){
DFS(T);
}
}
}
int main(){
pw[0]=1;
int n,MOD,q,phi,ct,ans=0;
cin>>n>>MOD;
for(R i=1;i!=n;i++){
pw[i]=pw[i-1]<<1;
if(pw[i]>P){
pw[i]-=P;
}
}
q=GetP(MOD);
phi=MOD-MOD/q;
unordered_map<int,int>Q,B;
vector<int>D;
for(int i=1;i*i<=phi;i++){
if(phi%i==0){
D.push_back(i);
if(i*i!=phi){
D.push_back(phi/i);
}
}
}
sort(D.begin(),D.end());
for(R i=0;i!=n;i++){
cin>>a[i];
if(a[i]%q==0){
Q[a[i]]=i;
d[i]=-1;
}
}
for(int i=0;i!=n;i++){
if(d[i]==-1){
for(int j=(L)a[i]*a[i]%MOD;j!=0;j=(L)j*a[i]%MOD){
if(Q.count(j)!=0){
G[Q[j]].push_back(i);
}
}
}else{
if(a[i]!=1){
int pw=1,cur=0;
for(int T:D){
pw=(L)pw*PowMod(a[i],T-cur,MOD)%MOD;
if(pw==1){
d[i]=T;
break;
}
cur=T;
}
}
B[d[i]]++;
}
}
for(int i=0;i!=n;i++){
if(d[i]>0){
for(R j=0;j!=n;j++){
if(i!=j&&d[j]!=-1&&(d[i]==d[j]&&i<j||d[i]>d[j]&&(d[j]==0||d[i]%d[j]==0))){
G[j].push_back(i);
}
}
}
}
unordered_set<int>S;
for(R i=0;i!=n;i++){
if(S.count(d[i])==0){
if(d[i]!=-1){
S.insert(d[i]);
}
for(R j=0;j!=n;j++){
vis[j]=false;
}
DFS(i);
int sz=d[i]==-1?1:B[d[i]];
ct=0;
for(R j=0;j!=n;j++){
if(vis[j]==true){
ct++;
}
}
ct=n-ct;
for(R j=0;j!=sz;j++){
ans+=pw[ct-j];
if(ans>=P){
ans-=P;
}
}
}
}
cout<<ans;
return 0;
}
WC2020 选课
显然是个背包题。
首先将不涉及课程限制的分类的背包先预处理出来,再将涉及的分类中不涉及限制的课程预处理出来。枚举带限制的课程的选课情况,选中的加入分类的背包,再合起来统计答案即可。
由于学分只有
1
,
2
,
3
1,2,3
1,2,3,最开始的背包需要贪心预处理。当没有三学分的课时,如果总学分是奇数就先选一个一学分的课,之后就将两个一学分的课绑定在一起当做一个二学分的课贪心即可,总学分是偶数的情况类似。然后枚举三学分的课总数即可。
令
L
=
T
−
∑
s
L=T-\sum s
L=T−∑s,时间复杂度
O
(
N
log
2
N
+
N
L
+
M
L
2
+
2
P
P
L
2
)
O(N \log_2 N+NL+ML^2+2^P PL^2)
O(Nlog2N+NL+ML2+2PPL2),空间复杂度
O
(
N
+
M
L
+
P
)
O(N+ML+P)
O(N+ML+P)
#include<stdio.h>
#include<vector>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
#define R register int
#define N 50000
#define INF 999999999
struct Course{
int Val,Cost;
};
inline auto Read(){
Course res;
scanf("%d%d",&res.Val,&res.Cost);
return res;
}
vector<Course>C[N];
struct Limits{
int Ax,Ay,Bx,By,Delta,Type;
inline void Read(){
scanf("%d%d%d%d%d",&Type,&Ax,&Ay,&Bx,&By);
Ax--;
Ay--;
Bx--;
By--;
if(Type!=3){
scanf("%d",&Delta);
if(Type==1){
Delta=-Delta;
}
}
}
}lim[66];
int lf[N],rt[N],dp[1500043],ref[N],pos[12],sel[12];
bool tag[50000];
vector<int>E[N],F[N],H[12];
int main(){
int n,tot,m,p,sum,row=0;
scanf("%d%d",&n,&tot);
sum=tot;
for(R i=0;i!=n;i++){
scanf("%d%d",&m,lf+i);
tot-=lf[i];
p=lf[i];
for(R j=0;j!=m;j++){
C[i].push_back(Read());
p-=C[i][j].Val;
sum-=C[i][j].Val;
}
if(p>0){
printf("-1");
return 0;
}
}
if(sum>0){
printf("-1");
return 0;
}
if(tot<0){
tot=0;
}
scanf("%d",&p);
set<pair<int,int>>TempS;
map<pair<int,int>,int>Q;
for(R i=0;i!=p;i++){
lim[i].Read();
tag[lim[i].Ax]=true;
tag[lim[i].Bx]=true;
TempS.insert(make_pair(lim[i].Ax,lim[i].Ay));
TempS.insert(make_pair(lim[i].Bx,lim[i].By));
}
int ct=0;
for(auto T:TempS){
E[T.first].push_back(T.second);
Q[T]=ct;
ct++;
}
vector<int>G(tot+1,INF);
G[0]=0;
for(R i=0;i!=n;i++){
rt[i]=lf[i]+tot;
for(int T:E[i]){
lf[i]-=C[i][T].Val;
}
if(lf[i]<0){
lf[i]=0;
}
int s=C[i].size();
auto T=E[i].begin();
vector<int>A[3];
for(R j=0;j!=s;j++){
while(T!=E[i].end()&&*T<j){
T++;
}
if(T==E[i].end()||*T!=j){
A[C[i][j].Val-1].push_back(C[i][j].Cost);
}
}
sort(A[0].begin(),A[0].end());
sort(A[2].begin(),A[2].end());
vector<int>B=A[1];
for(R j=2;j<A[0].size();j+=2){
B.push_back(A[0][j-1]+A[0][j]);
}
sort(B.begin(),B.end());
for(R j=1;j!=rt[i]+3;j++){
dp[j]=INF;
}
if(A[0].empty()==false){
sum=0;
s=B.size();
for(R j=1;j<rt[i]+3;j+=2){
dp[j]=A[0][0]+sum;
if(j>>1>=s){
break;
}
sum+=B[j>>1];
}
}
B=A[1];
for(R j=1;j<A[0].size();j+=2){
B.push_back(A[0][j-1]+A[0][j]);
}
sort(B.begin(),B.end());
sum=0;
s=B.size();
for(R j=0;j<rt[i]+3;j+=2){
dp[j]=sum;
if(j>>1>=s){
break;
}
sum+=B[j>>1];
}
for(R j=rt[i]+1;j!=-1;j--){
if(dp[j]>dp[j+1]){
dp[j]=dp[j+1];
}
}
s=A[2].size();
for(R j=lf[i];j<=rt[i];j++){
int f=INF,g;
sum=0;
for(R k=0;k*3<j+3;k++){
g=(k*3>j?0:dp[j-k*3])+sum;
if(g<f){
f=g;
}
if(k==s){
break;
}
sum+=A[2][k];
}
F[i].push_back(f);
}
if(tag[i]==false){
vector<int>H(tot+1,INF);
for(R j=0;j<=tot;j++){
for(R k=0;k<=tot-j;k++){
int f=F[i][j]+G[k];
if(f<H[j+k]){
H[j+k]=f;
}
}
}
G.swap(H);
}else{
ref[i]=row;
pos[row]=i;
row++;
}
}
int ans=INF;
for(R i=0;i!=1<<ct;i++){
int adds=0;
bool tag=true;
for(R j=0;j!=row;j++){
sel[j]=0;
}
for(R j=0;j!=p;j++){
int x=Q[make_pair(lim[j].Ax,lim[j].Ay)],y=Q[make_pair(lim[j].Bx,lim[j].By)];
if((i>>x&i>>y&1)==1){
if(lim[j].Type==3){
tag=false;
break;
}
adds+=lim[j].Delta;
}
}
if(tag==true){
auto T=TempS.begin();
for(R j=0;j!=ct;j++){
if((i>>j&1)==1){
int x=T->first,y=T->second;
sel[ref[x]]+=C[x][y].Val;
adds+=C[x][y].Cost;
}
T++;
}
for(R j=0;j!=row;j++){
vector<int>().swap(H[j]);
int x=pos[j],s;
s=F[x].size();
for(R k=rt[x]-tot;k<=rt[x];k++){
int y=k-rt[x]+s-1-sel[j];
if(y<0){
H[j].push_back(0);
}else{
H[j].push_back(F[x][y]);
}
}
}
vector<int>Cur=G;
for(R j=0;j!=row;j++){
vector<int>B(tot+1,INF);
for(R k=0;k<=tot;k++){
for(R l=0;l<=tot-k;l++){
int f=H[j][k]+Cur[l];
if(f<B[k+l]){
B[k+l]=f;
}
}
}
Cur.swap(B);
}
if(Cur[tot]+adds<ans){
ans=Cur[tot]+adds;
}
}
}
printf("%d",ans);
return 0;
}
线性规划问题
给出三个长度为
n
n
n 的序列
a
,
b
,
c
a,b,c
a,b,c 和整数
p
p
p,满足:
{
∑
i
=
1
n
a
i
x
i
⩽
p
∑
i
=
1
n
b
i
x
i
⩾
p
w
=
∑
i
=
1
n
c
i
x
i
x
i
∈
{
0
,
1
}
\begin{cases} \sum_{i=1}^n a_i x_i \leqslant p \\ \sum_{i=1}^n b_i x_i \geqslant p \\ w=\sum_{i=1}^n c_i x_i x_i \in \{ 0,1 \} \end{cases}
⎩⎪⎨⎪⎧∑i=1naixi⩽p∑i=1nbixi⩾pw=∑i=1ncixixi∈{0,1}
求
w
w
w 的最小值或判断无解。
1
⩽
n
⩽
1000
,
c
i
⩽
1
0
6
,
a
i
⩽
b
i
⩽
1
0
4
,
p
⩽
1
0
4
1 \leqslant n \leqslant 1000,c_i \leqslant 10^6,a_i \leqslant b_i \leqslant 10^4,p \leqslant 10^4
1⩽n⩽1000,ci⩽106,ai⩽bi⩽104,p⩽104
时间限制1s,空间限制512MB。
由于
a
i
⩽
b
i
a_i \leqslant b_i
ai⩽bi 且 最后选出的
a
,
b
a,b
a,b 的和在
p
p
p 的两边,设
f
i
,
j
f_{i,j}
fi,j 表示考虑到了第
i
i
i 个数,之前
a
a
a 的和不超过
j
j
j,
b
b
b 的和不小于
j
j
j 的
w
w
w 最小值。转移很简单,但需要单调队列优化。
时间复杂度
O
(
n
p
)
O(np)
O(np),空间复杂度
O
(
p
)
O(p)
O(p)
#include<stdio.h>
#define R register int
#define INF 999999999
int a[1000],b[1000],c[1000],f[10001],q[10001];
inline void Solve(){
int n,m,ans=INF,head=0,tail=0,cur;
scanf("%d%d",&n,&m);
for(R i=0;i!=n;i++){
scanf("%d",a+i);
}
for(R i=0;i!=n;i++){
scanf("%d",b+i);
}
for(R i=0;i!=n;i++){
scanf("%d",c+i);
}
for(R i=1;i<=m;i++){
f[i]=INF;
}
for(R i=0;i!=n;i++){
head=0;
tail=-1;
cur=m;
for(R j=m;j!=-1;j--){
while(cur>=j-b[i]&&cur!=-1){
while(head<=tail&&f[q[tail]]>=f[cur]){
tail--;
}
tail++;
q[tail]=cur;
cur--;
}
while(head<=tail&&q[head]>j-a[i]){
head++;
}
if(head<=tail&&f[q[head]]+c[i]<f[j]){
f[j]=f[q[head]]+c[i];
}
}
}
if(f[m]==INF){
puts("IMPOSSIBLE!!!");
}else{
printf("%d\n",f[m]);
}
}
int main(){
int t;
scanf("%d",&t);
for(R i=0;i!=t;i++){
Solve();
}
return 0;
}
操作
有一个初始为零的变量
x
x
x,
n
n
n 个操作,每个操作有
P
i
P_i
Pi 的概率使
x
x
x 变为
x
+
A
i
x+A_i
x+Ai,
1
−
P
i
1-P_i
1−Pi 的概率使
x
x
x 变为
B
i
x
B_i x
Bix。随机顺序执行所有操作,求最终
x
x
x 的期望模
998244353
998244353
998244353的值。
1
⩽
n
⩽
1
0
5
1 \leqslant n \leqslant 10^5
1⩽n⩽105
A
i
,
B
i
,
P
i
⩽
998244352
A_i,B_i,P_i \leqslant 998244352
Ai,Bi,Pi⩽998244352
时间限制2s,空间限制512MB。
考虑一个数对答案的贡献。其他的操作如果在该操作之后,则会对该操作造成
P
+
(
1
−
P
)
B
P+(1-P)B
P+(1−P)B 的期望倍数。若有
k
k
k 个操作在该操作之后则有
k
!
(
n
−
k
)
!
k!(n-k)!
k!(n−k)! 的排列方案数,因此在该操作之后的操作数量对答案有影响,因此考虑多项式。
设
Q
i
(
x
)
=
P
i
B
i
∏
1
⩽
j
⩽
n
,
j
≠
i
(
1
+
(
P
j
+
(
1
−
P
j
)
B
j
)
x
)
,
F
(
x
)
=
∑
i
=
1
n
Q
i
(
x
)
Q_i(x)=P_i B_i \prod_{1 \leqslant j \leqslant n,j \neq i}(1+(P_j+(1-P_j)B_j)x),F(x)=\sum_{i=1}^n Q_i(x)
Qi(x)=PiBi∏1⩽j⩽n,j=i(1+(Pj+(1−Pj)Bj)x),F(x)=∑i=1nQi(x),则答案为
∑
i
=
0
n
−
1
i
!
(
n
−
1
−
i
)
!
[
x
i
]
F
(
x
)
\sum_{i=0}^{n-1} i!(n-1-i)![x^i]F(x)
∑i=0n−1i!(n−1−i)![xi]F(x)。分治NTT求
F
(
x
)
F(x)
F(x) 即可。
时间复杂度
O
(
n
log
2
2
n
)
O(n \log^2_2 n)
O(nlog22n),空间复杂度
O
(
n
log
2
n
)
O(n \log_2 n)
O(nlog2n)
#include<stdio.h>
#include<vector>
using namespace std;
#define R register int
#define I inline
#define L long long
#define N 131072
#define P 998244353
I void Swap(int&x,int&y){
int t=x;
x=y;
y=t;
}
I int Add(int x,const int y){
x+=y;
return x<P?x:x-P;
}
I int PowMod(int x,int y){
int res=1;
while(y!=0){
if((y&1)==1){
res=(L)res*x%P;
}
y>>=1;
x=(L)x*x%P;
}
return res;
}
int d[N],p[N],b[N],fac[N];
I void NTT(int*A,const int len,const int type){
int tem=0;
for(R i=0;i!=len;i++){
if(i<tem){
Swap(A[i],A[tem]);
}
int p=len;
do{
p>>=1;
tem^=p;
}while(tem<p);
}
static int w[N];
w[0]=1;
for(R i=1;i!=len;i<<=1){
int omg=PowMod(3,P-1+(P-1)*type/(i<<1));
for(R j=i-2>>1;j!=-1;j--){
w[j<<1|1]=(L)w[j]*omg%P;
w[j<<1]=w[j];
}
for(R j=0;j!=len;j+=i<<1){
for(R k=j;k!=i+j;k++){
int t1=A[k],t2=(L)A[i+k]*w[k-j]%P;
A[k]=Add(t1,t2);
A[i+k]=Add(t1,P-t2);
}
}
}
if(type==-1){
tem=PowMod(len,P-2);
for(R i=0;i!=len;i++){
A[i]=(L)A[i]*tem%P;
}
}
}
I void Copy(vector<int>&A,int*B){
int s=A.size();
for(R i=0;i!=s;i++){
B[i]=A[i];
}
}
I void Solve(int l,int r,vector<int>&A,vector<int>&S){
if(l==r){
A.push_back(1);
A.push_back(d[l]);
S.push_back((L)p[r]*b[r]%P);
}else{
vector<int>LP,RP,LS,RS;
int mid=l+r>>1,len=1,t;
Solve(l,mid,LP,LS);
Solve(mid+1,r,RP,RS);
t=LP.size()+RP.size()-1;
while(len<t){
len<<=1;
}
static int a[N],b[N],c[N],d[N],sp[N],ss[N];
for(R i=0;i!=len;i++){
a[i]=b[i]=c[i]=d[i]=0;
}
Copy(LP,a);
Copy(LS,b);
Copy(RP,c);
Copy(RS,d);
NTT(a,len,1);
NTT(b,len,1);
NTT(c,len,1);
NTT(d,len,1);
for(R i=0;i!=len;i++){
sp[i]=(L)a[i]*c[i]%P;
ss[i]=((L)a[i]*d[i]+(L)b[i]*c[i])%P;
}
NTT(sp,len,-1);
NTT(ss,len,-1);
A.resize(t);
t--;
S.resize(t);
for(R i=0;i!=t;i++){
S[i]=ss[i];
A[i]=sp[i];
}
A[t]=sp[t];
}
}
int main(){
fac[0]=1;
int n,c,ans=0;
scanf("%d",&n);
for(R i=1;i<=n;i++){
fac[i]=(L)fac[i-1]*i%P;
}
for(R i=0;i!=n;i++){
scanf("%d%d%d",p+i,b+i,&c);
d[i]=((1ll+P-p[i])*c+p[i])%P;
}
vector<int>A,S;
Solve(0,n-1,A,S);
for(R i=0;i!=n;i++){
ans=((L)S[i]*fac[i]%P*fac[n-1-i]+ans)%P;
}
printf("%d",(L)ans*PowMod(fac[n],P-2)%P);
return 0;
}
Count Number of Sequences
对于每个询问求出
f
f
f 的迭代,然后在点分树上数每个值的个数最后DP即可。
时间复杂度
O
(
n
log
2
3
n
)
O(n \log_2^3 n)
O(nlog23n),空间复杂度
O
(
n
log
2
n
)
O(n \log_2 n)
O(nlog2n)
#include<stdio.h>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 100001
#define P 1000000007
I int GetInv(int x){
int res=1,y=P-2;
while(y!=0){
if((y&1)==1){
res=(L)res*x%P;
}
x=(L)x*x%P;
y>>=1;
}
return res;
}
int val[N],fac[N],invf[N],trans[N],pref[N],sz[N],fa[N],h[N],dep[N],f[N],Top[N];
vector<int>G[N],D[N];
bool vis[N];
I void GetCen(int x,int F,int&cen,const int n){
sz[x]=1;
int maxsize=0;
for(int T:G[x]){
if(T!=F&&vis[T]==false){
GetCen(T,x,cen,n);
sz[x]+=sz[T];
if(sz[T]>maxsize){
maxsize=sz[T];
}
}
}
if(n-sz[x]>maxsize){
maxsize=n-sz[x];
}
if(maxsize<<1<=n){
cen=x;
}
}
I void DFS(int x,int F,int dep){
if(dep!=0){
D[x].push_back(dep);
}
for(int T:G[x]){
if(T!=F&&vis[T]==false){
DFS(T,x,dep+1);
}
}
}
I int ConqTree(int x,const int n){
int cen;
GetCen(x,0,cen,n);
DFS(cen,0,0);
vis[cen]=true;
for(int T:G[cen]){
if(vis[T]==false){
fa[ConqTree(T,sz[T]<sz[cen]?sz[T]:n-sz[cen])]=cen;
}
}
return cen;
}
map<int,vector<int>>Pos[N],Neg[N];
I void Insert(map<int,vector<int>>&Q,int r,int v){
Q[v].push_back(r);
}
I int Add(int d,map<int,vector<int>>&Q,const int r){
if(Q.count(d)!=0){
vector<int>&V=Q[d];
return upper_bound(V.begin(),V.end(),r)-V.begin();
}
return 0;
}
int main(){
fac[0]=pref[0]=1;
for(int i=1;i!=N;i++){
for(R j=i<<1;j<N;j+=i){
if(trans[j]<i){
trans[j]=i;
}
}
}
int n,x,y,q;
scanf("%d",&n);
for(R i=1;i<=n;i++){
scanf("%d",val+i);
fac[i]=(L)fac[i-1]*i%P;
}
invf[n]=GetInv(fac[n]);
for(R i=n;i!=0;i--){
invf[i-1]=(L)invf[i]*i%P;
}
for(R i=1;i<=n;i++){
pref[i]=pref[i-1]+invf[i];
if(pref[i]>P){
pref[i]-=P;
}
}
for(R i=1;i<=n;i++){
pref[i]=(L)pref[i]*fac[i]%P;
}
for(R i=1;i!=n;i++){
scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
ConqTree(1,n);
for(R i=1;i<=n;i++){
Insert(Pos[i],0,val[i]);
reverse(D[i].begin(),D[i].end());
int cur=0;
for(R j=i;fa[j]!=0;j=fa[j]){
x=D[i][cur];
cur++;
D[i].push_back(x);
Insert(Pos[fa[j]],x,val[i]);
Insert(Neg[j],x,val[i]);
}
}
for(R i=1;i<=n;i++){
for(auto&T:Pos[i]){
sort(T.second.begin(),T.second.end());
}
for(auto&T:Neg[i]){
sort(T.second.begin(),T.second.end());
}
}
scanf("%d",&q);
for(R i=0;i!=q;i++){
scanf("%d%d",&x,&y);
vector<int>A;
for(R j=val[x];j!=0;j=trans[j]){
int v=Add(j,Pos[x],y),cur=0;
for(R k=x;fa[k]!=0;k=fa[k]){
int d=D[x][cur];
cur++;
v+=Add(j,Pos[fa[k]],y-d)-Add(j,Neg[k],y-d);
}
if(v==0){
break;
}
A.push_back(v);
}
int ans=pref[A[0]-1],cur,s=A.size();
cur=ans;
for(R j=1;j!=s;j++){
cur=(pref[A[j]]-1ll+P)*cur%P;
ans+=cur;
if(ans>=P){
ans-=P;
}
}
printf("%d\n",ans);
}
return 0;
}
Tree and Brilliant Array
显然对于每个质因数在树上的方案数是独立的。设
f
i
,
j
,
k
f_{i,j,k}
fi,j,k 表示以点
i
i
i 为根,某质因数幂和为
j
j
j,根的幂为
k
k
k 的方案数。这可以用树上DP解决。
然后解决质因数合并的问题。设
g
i
g_i
gi 表示不考虑根,
[
K
∏
j
=
2
n
a
j
]
=
i
[\frac{K}{\prod_{j=2}^n a_j}]=i
[∏j=2najK]=i 的方案数。用map替代数组结合
f
f
f 即可转移。最终答案为
∑
i
i
g
i
\sum_{i} i g_i
∑iigi。
时间复杂度
O
(
N
log
2
4
K
+
K
log
2
K
)
O(N \log_2^4K+\sqrt K \log_2 K)
O(Nlog24K+Klog2K),空间复杂度
O
(
N
log
2
2
K
+
K
)
O(N \log_2^2 K+\sqrt K)
O(Nlog22K+K)
#include<stdio.h>
#include<vector>
#include<map>
#include<math.h>
using namespace std;
#define R register int
#define L long long
#define I inline
#define P 998244353
vector<int>G[1001];
int dp[1001][40][40],res[40],tdp[40][40];
I void DP(int x){
dp[x][0][0]=1;
for(int T:G[x]){
DP(T);
for(R i=0;i!=40;i++){
for(R j=0;j<=i;j++){
if(dp[T][i][j]!=0){
for(R k=0;k!=40-i;k++){
for(R l=0;l<=k;l++){
int&g=tdp[i+k][j>l?j:l];
g=((L)dp[T][i][j]*dp[x][k][l]+g)%P;
}
}
}
}
}
for(R i=0;i!=40;i++){
for(R j=0;j!=40;j++){
dp[x][i][j]=tdp[i][j];
tdp[i][j]=0;
}
}
}
for(R i=0;i!=40;i++){
for(R j=0;j<=i;j++){
int lim=x==1?j+1:40;
if(lim>40-i){
lim=40-i;
}
for(R k=j;k<lim;k++){
tdp[i+k][k]+=dp[x][i][j];
if(tdp[i+k][k]>=P){
tdp[i+k][k]-=P;
}
}
}
}
for(R i=0;i!=40;i++){
for(R j=0;j!=40;j++){
dp[x][i][j]=tdp[i][j];
tdp[i][j]=0;
}
}
}
bool vis[1000001];
int main(){
int n,x,m,ct=0,ans=0;
scanf("%d",&n);
L k;
scanf("%lld",&k);
m=sqrt(k);
for(int i=2;i<=n;i++){
scanf("%d",&x);
G[x].push_back(i);
}
DP(1);
for(R i=0;i!=40;i++){
for(R j=0;j<=i;j++){
res[i]+=dp[1][i][j];
if(res[i]>=P){
res[i]-=P;
}
}
}
map<L,int>F;
F[k]=1;
for(R i=2;i<=m;i++){
if(vis[i]==false){
for(R j=i<<1;j<=m;j+=i){
vis[j]=true;
}
L pie=(L)i*i;
vector<pair<L,int>>V;
for(R j=2;pie<=k;j++){
for(auto T=F.lower_bound(pie);T!=F.end();T++){
V.push_back(make_pair(T->first/pie,(L)T->second*res[j]%P));
}
pie*=i;
}
for(auto T:V){
int&dp=F[T.first];
dp+=T.second;
if(dp>=P){
dp-=P;
}
}
}
}
for(auto T:F){
ans=(T.first%P*T.second+ans)%P;
}
printf("%d",ans);
return 0;
}
用前缀和优化可以将时间复杂度做到 O ( N log 2 3 K + K log 2 K ) O(N \log_2^3 K+\sqrt K \log_2 K) O(Nlog23K+Klog2K)。
WF2014 Bagage
当
n
⩽
7
n \leqslant 7
n⩽7 时,有解可以暴搜。当
n
⩾
4
n \geqslant 4
n⩾4 时有将所有物品最终放到
[
−
1
,
2
n
−
2
]
[-1,2n-2]
[−1,2n−2] 的方案。
当
n
⩾
8
n \geqslant 8
n⩾8 时,利用递归构造可以构造出方案:
++BA[BABABA…]BABA
ABBA[BABABA…]B++A
ABBA[++BABA…]BBAA
ABBA[AA…BB…++]BBAA
A++A[AA…BB…BB]BBAA
AAAA[AA…BB…BB]BB
中括号内的部分为递归构造的部分。这样做的步数为
n
n
n。可以证明答案不会比
n
n
n 更小。
时空复杂度
O
(
n
)
O(n)
O(n)
#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;
#define R register int
#define I inline
I void Solve(int n,int b){
if(n==4){
printf("%d to %d\n%d to %d\n%d to %d\n%d to %d\n",b+6,b-1,b+3,b+6,b,b+3,b+7,b);
}else if(n==5){
printf("%d to %d\n%d to %d\n%d to %d\n%d to %d\n%d to %d\n",b+8,b-1,b+3,b+8,b+6,b+3,b,b+6,b+9,b);
}else if(n==6){
printf("%d to %d\n%d to %d\n%d to %d\n%d to %d\n%d to %d\n%d to %d\n",b+10,b-1,b+7,b+10,b+2,b+7,b+6,b+2,b,b+6,b+11,b);
}else if(n==7){
printf("%d to %d\n%d to %d\n%d to %d\n%d to %d\n%d to %d\n%d to %d\n%d to %d\n",b+8,b-1,b+5,b+8,b+12,b+5,b+3,b+12,b+9,b+3,b,b+9,b+13,b);
}else{
printf("%d to %d\n%d to %d\n",(n<<1)-2+b,b-1,b+3,(n<<1)-2+b);
Solve(n-4,b+4);
printf("%d to %d\n%d to %d\n",b,(n<<1)-5+b,(n<<1)-1+b,b);
}
}
int main(){
int n;
scanf("%d",&n);
if(n==3){
printf("2 to -1\n5 to 2\n3 to -3");
}else{
Solve(n,0);
}
return 0;
}
Hotel加强版
设
f
i
,
j
f_{i,j}
fi,j 表示以
i
i
i 为根的子树中与
i
i
i 距离为
j
j
j 的点数,
g
i
,
j
g_{i,j}
gi,j 表示以
i
i
i 为根的子树中已匹配且再向上走
j
j
j 个点即可计入答案的方案数。DP转移用长链剖分优化。
时空复杂度
O
(
n
)
O(n)
O(n)
#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define N 100001
vector<int>H[N];
deque<int>F[N];
deque<L>G[N];
int dep[N];
inline void DP(int x,int fa,L&ans){
int hx=0;
for(int T:H[x]){
if(T!=fa){
DP(T,x,ans);
if(dep[T]>dep[x]){
hx=T;
dep[x]=dep[T];
}
}
}
dep[x]++;
if(hx!=0){
F[x].swap(F[hx]);
G[x].swap(G[hx]);
G[x].pop_front();
}
F[x].push_front(1);
G[x].resize(dep[x]);
ans+=G[x][0];
for(int T:H[x]){
if(T!=fa&&T!=hx){
int s=dep[T];
for(R i=0;i!=s;i++){
if(i!=0){
ans+=G[T][i]*F[x][i-1];
}
ans+=G[x][i+1]*F[T][i];
}
for(R i=0;i!=s;i++){
if(i!=0){
G[x][i-1]+=G[T][i];
}
G[x][i+1]+=(L)F[T][i]*F[x][i+1];
F[x][i+1]+=F[T][i];
}
deque<int>().swap(F[T]);
deque<L>().swap(G[T]);
}
}
}
int main(){
int n,x,y;
scanf("%d",&n);
for(R i=1;i!=n;i++){
scanf("%d%d",&x,&y);
H[x].push_back(y);
H[y].push_back(x);
}
L ans=0;
DP(1,0,ans);
printf("%lld",ans);
return 0;
}
Arietta
容易发现一种网络流的建图。为了解决子树的限制,用树上线段树优化建图即可。
空间复杂度
O
(
n
log
2
n
)
O(n \log_2 n)
O(nlog2n)
#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 10001
#define K 700000
#define M 3000000
#define INF 999999999
I int Min(const int x,const int y){
return x<y?x:y;
}
vector<int>G[N];
int root[N],val[N],End[M],Ecur[K],Cap[M],Next[M],Last[K],Eucr[K],gap[K],dis[K],ECT=1,NCT=2;
struct Piano{
int ValL,ValR,Ti;
};
vector<Piano>C[N];
I void GetNode(int&x){
NCT++;
x=NCT;
}
I void Link(int x,int y,int c){
ECT++;
End[ECT]=y;
Cap[ECT]=c;
Next[ECT]=Last[x];
Last[x]=ECT;
ECT++;
End[ECT]=x;
Next[ECT]=Last[y];
Last[y]=ECT;
}
struct SegmentNode{
int Ls,Rs;
}t[K];
I void Insert(int p,int lf,int rt,const int x){
if(lf==rt){
Link(p,2,1);
}else{
int mid=lf+rt>>1;
if(x>mid){
if(t[p].Rs==0){
GetNode(t[p].Rs);
Link(p,t[p].Rs,INF);
}
Insert(t[p].Rs,mid+1,rt,x);
}else{
if(t[p].Ls==0){
GetNode(t[p].Ls);
Link(p,t[p].Ls,INF);
}
Insert(t[p].Ls,lf,mid,x);
}
}
}
I int Merge(int x,int y){
if(x==0||y==0){
return x|y;
}
int p;
GetNode(p);
Link(p,x,INF);
Link(p,y,INF);
t[p].Ls=Merge(t[x].Ls,t[y].Ls);
t[p].Rs=Merge(t[x].Rs,t[y].Rs);
return p;
}
I void LinkSegment(int p,int lf,int rt,const int l,const int r,const int x){
if(l<=lf&&rt<=r){
Link(x,p,INF);
}else{
int mid=lf+rt>>1;
if(l<=mid&&t[p].Ls!=0){
LinkSegment(t[p].Ls,lf,mid,l,r,x);
}
if(r>mid&&t[p].Rs!=0){
LinkSegment(t[p].Rs,mid+1,rt,l,r,x);
}
}
}
I void DFS(int x,int F){
GetNode(root[x]);
Insert(root[x],1,N,val[x]);
for(int T:G[x]){
if(T!=F){
DFS(T,x);
root[x]=Merge(root[x],root[T]);
}
}
for(auto T:C[x]){
int p;
GetNode(p);
Link(1,p,T.Ti);
LinkSegment(root[x],1,N,T.ValL,T.ValR,p);
}
}
I bool BFS(){
for(R i=1;i<=NCT;i++){
dis[i]=NCT;
}
dis[2]=0;
queue<int>Q;
Q.push(2);
while(Q.empty()==false){
int u=Q.front();
Q.pop();
for(R i=Last[u];i!=0;i=Next[i]){
int v=End[i];
if(Cap[i^1]!=0&&dis[v]>dis[u]+1){
dis[v]=dis[u]+1;
Q.push(v);
}
}
}
for(R i=1;i<=NCT;i++){
Ecur[i]=Last[i];
}
return dis[1]<NCT;
}
I int Dinic(int u,int flow){
if(u==2){
return flow;
}
int tem=0;
for(R&i=Ecur[u];i!=0;i=Next[i]){
int v=End[i];
if(Cap[i]!=0&&dis[u]==dis[v]+1){
int f=Dinic(v,Min(flow-tem,Cap[i]));
tem+=f;
Cap[i]-=f;
Cap[i^1]+=f;
if(tem==flow){
break;
}
}
}
return tem;
}
I int MaxFlow(){
int res=0;
while(BFS()==true){
res+=Dinic(1,INF);
}
return res;
}
int main(){
int n,m,x;
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++){
scanf("%d",&x);
G[x].push_back(i);
}
for(R i=1;i<=n;i++){
scanf("%d",val+i);
}
for(R i=0;i!=m;i++){
Piano P;
scanf("%d%d%d%d",&P.ValL,&P.ValR,&x,&P.Ti);
C[x].push_back(P);
}
DFS(1,0);
printf("%d",MaxFlow());
return 0;
}
Poborcy podatkowi
有一棵
n
n
n 个点的树,边有边权。
请你找出一些不交的长为
4
4
4的路径,使得边权之和最大。
2
⩽
n
⩽
200000
2 \leqslant n \leqslant 200000
2⩽n⩽200000
时间限制2s,空间限制512MB。
设
f
i
,
j
f_{i,j}
fi,j 表示在以
i
i
i 为根的子树中选若干条长度为
4
4
4的链且有一条以
i
i
i 为端点长度为
j
j
j 的链的最大权值和。这类似于树上背包问题,DP时记录子树内选用
j
=
1
j=1
j=1 和
j
=
3
j=3
j=3 的个数差和
j
=
2
j=2
j=2 个数的奇偶性即可。这样直接DP是
O
(
n
2
)
O(n^2)
O(n2) 的。
事实上随机每个点儿子的顺序,
j
=
1
j=1
j=1 和
j
=
3
j=3
j=3 的个数差只记录到
O
(
n
)
O(\sqrt n)
O(n) 的范围正确率就足够高了。
时间复杂度
O
(
n
n
)
O(n \sqrt n)
O(nn),空间复杂度
O
(
n
)
O(n)
O(n)
#include<stdio.h>
#include<vector>
#include<random>
#include<algorithm>
#include<time.h>
using namespace std;
#define R register int
#define L long long
#define I inline
#define INF -999999999999999
I L Max(const L x,const L y){
return x>y?x:y;
}
struct Edge{
int End,Len;
};
vector<Edge>G[200001];
L f[200001][4],g[801][2],h[801][2];
I void Insert(int x,int w,const int l,const int r){
L val[4];
val[0]=Max(f[x][3]+w,f[x][0]);
for(R i=1;i!=4;i++){
val[i]=f[x][i-1]+w;
}
for(R i=l;i<=r;i++){
for(R j=0;j!=2;j++){
h[i][j]=Max(g[i][j]+val[0],g[i][j^1]+val[2]);
}
}
for(R i=l;i!=r;i++){
for(R j=0;j!=2;j++){
h[i][j]=Max(h[i][j],g[i+1][j]+val[1]);
}
}
for(R i=r;i!=l;i--){
for(R j=0;j!=2;j++){
h[i][j]=Max(h[i][j],g[i-1][j]+val[3]);
}
}
for(R i=l;i<=r;i++){
for(R j=0;j!=2;j++){
g[i][j]=h[i][j];
}
}
}
I void DP(int x,int F){
int v,l=401-G[x].size(),r=399+G[x].size();
if(l<0){
l=0;
}
if(r>800){
r=800;
}
for(Edge T:G[x]){
v=T.End;
if(v!=F){
DP(v,x);
}
}
for(R i=l;i<=r;i++){
g[i][0]=g[i][1]=INF;
}
g[400][0]=0;
for(Edge T:G[x]){
v=T.End;
if(v!=F){
Insert(v,T.Len,l,r);
}
}
f[x][0]=g[400][0];
f[x][1]=l!=400?g[399][0]:INF;
f[x][2]=g[400][1];
f[x][3]=r!=400?g[401][0]:INF;
}
int main(){
int n,x,y,z;
scanf("%d",&n);
auto Link=[](int x,int y,int z){
Edge E;
E.Len=z;
E.End=y;
G[x].push_back(E);
E.End=x;
G[y].push_back(E);
};
for(R i=1;i!=n;i++){
scanf("%d%d%d",&x,&y,&z);
Link(x,y,z);
}
mt19937 Rand(time(0));
for(R i=1;i<=n;i++){
shuffle(G[i].begin(),G[i].end(),Rand);
}
DP(1,0);
printf("%lld",f[1][0]);
return 0;
}