冬令营系列题目

题目:
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=Ts,时间复杂度 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=1naixipi=1nbixipw=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 1n1000,ci106,aibi104,p104
时间限制1s,空间限制512MB。

由于 a i ⩽ b i a_i \leqslant b_i aibi 且 最后选出的 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 1Pi 的概率使 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 1n105
A i , B i , P i ⩽ 998244352 A_i,B_i,P_i \leqslant 998244352 Ai,Bi,Pi998244352
时间限制2s,空间限制512MB。

考虑一个数对答案的贡献。其他的操作如果在该操作之后,则会对该操作造成 P + ( 1 − P ) B P+(1-P)B P+(1P)B 的期望倍数。若有 k k k 个操作在该操作之后则有 k ! ( n − k ) ! k!(n-k)! k!(nk)! 的排列方案数,因此在该操作之后的操作数量对答案有影响,因此考虑多项式。
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)=PiBi1jn,j=i(1+(Pj+(1Pj)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=0n1i!(n1i)![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+K log2K),空间复杂度 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+K log2K)

WF2014 Bagage

n ⩽ 7 n \leqslant 7 n7 时,有解可以暴搜。当 n ⩾ 4 n \geqslant 4 n4 时有将所有物品最终放到 [ − 1 , 2 n − 2 ] [-1,2n-2] [1,2n2] 的方案。
n ⩾ 8 n \geqslant 8 n8 时,利用递归构造可以构造出方案:
++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 2n200000
时间限制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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值