省选测试19

A. 战略游戏

分析

考虑枚举树上的两个点,强制规定这两个点路径上的边必须经过 \(k\)

设这两个点分别为 \(u,v\)

那么我们要在 \(u\) 的一侧选择 \(k\) 条不相交的路径,在 \(v\) 的一侧选择 \(k\) 条不相交的路径,把它们拼在一起

先随便规定一个根节点,树就有了父子关系

对于一个节点 \(u\) ,在它的子树内选择 \(k\) 条不相交路径的生成函数为 \(\prod (size[son[u]]x+1)\)

这个东西可以用分治 \(fft\) 做,类似于线段树

但是要注意这个生成函数求出来的结果是不包括一个点的情况的,但实际上我们是可以只选择一个点 \(u\)

理解的话可以看成有一个容量为 \(k\) 的背包,选择一个子树就会花费 \(1\) 的体积,并且方案数乘上子树大小

\(a[i]\) 为该多项式第 \(i\) 项的次数,\(val[u]\) 为在 \(u\) 的子树内选择 \(k\) 条不相交的路径(可以是单独的点)的方案数

那么 \(val[u]= \sum\limits_{i=0}^ka[i]A_k^i\)

因为 \(k\) 的选择是有序的,所以乘上一个排列数

现在考虑怎么把 \(n^2\) 种配对情况不重不漏的计算上

如果 \(u,v\) 之间没有祖先关系,那么它们之间子树的选择是互不影响的,直接在它们的 \(lca\) 处把这个贡献乘上就行了

否则可以按照换根 \(dp\) 的套路去处理

如果 \(u\)\(v\) 的祖先,那么我们就要把 \(u\) 的生成函数中 \(v\) 的 那一项除掉,再把 \(n-size[u]\) 这一项加上

因为乘和除的都是一个一次多项式,所以过程可以手动模拟,单次计算是 \(O(degree)\)

但是这样做总的复杂度还是不对

考虑最极端的菊花图,根节点的生成函数的项的个数是 \(O(n)\) 级别的

它的儿子节点的个数也是 \(O(n)\) 级别的,这样复杂度就变成 \(O(n^2)\)

所以我们要将一个节点的所有儿子节点的子树从小到大排好序,之前计算过的就不要计算了

注意到不同的 \(size[son]\) 只有 \(\sqrt{n}\)

所以算上分治 \(fft\) ,总复杂度就是 \(O(n\sqrt{n}+nlog^2n)\)

但是这种做法还有一个 \(bug\)

因为我们把经过 \(k\) 次的和经过 \(1\) 次的看成是不同的情况

所以当 \(k=1\) 时会多算,特殊处理一下就行了

代码

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<vector>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=3e5+5,mod=998244353,G=3;
int w[25][maxn],wz[maxn];
inline int addmod(rg int now1,rg int now2){
	return now1+=now2,now1>=mod?now1-mod:now1;
}
inline int delmod(rg int now1,rg int now2){
	return now1-=now2,now1<0?now1+mod:now1;
}
inline int mulmod(rg long long now1,rg int now2){
	return now1*=now2,now1>=mod?now1%mod:now1;
}
inline int ksm(rg int ds,rg int zs){
	rg int nans=1;
	while(zs){
		if(zs&1) nans=mulmod(nans,ds);
		ds=mulmod(ds,ds);
		zs>>=1;
	}
	return nans;
}
void ntt(std::vector<int>&A,rg int lim,rg int typ){
	for(rg int i=0;i<lim;i++) if(i<wz[i]) std::swap(A[i],A[wz[i]]);
	for(rg int len=1,t0=0;len<lim;len<<=1,t0++){
		for(rg int j=0,now=len<<1;j<lim;j+=now){
			for(rg int o=0;o<len;o++){
				rg int x=A[j+o],y=mulmod(A[j+o+len],w[t0][o]);
				A[j+o]=addmod(x,y),A[j+o+len]=delmod(x,y);
			}
		}
	}
	if(typ==-1){
		rg int ny=ksm(lim,mod-2);
		std::reverse(A.begin()+1,A.end());
		for(rg int i=0;i<lim;i++) A[i]=mulmod(A[i],ny);
	}
}
int jc[maxn],jcc[maxn],ny[maxn],n,k;
void pre1(){
	rg int mmax=1;
	for(;mmax<=n+n;mmax<<=1);
	for(rg int len=1,t0=0;len<mmax;len<<=1,t0++){
		w[t0][0]=1,w[t0][1]=ksm(G,(mod-1)/(len<<1));
		for(rg int j=1;j<len;j++) w[t0][j]=mulmod(w[t0][j-1],w[t0][1]);
	}
}
void pre2(){
	rg int mmax=k;
	ny[1]=1;
	for(rg int i=2;i<=mmax;i++) ny[i]=mulmod(mod-mod/i,ny[mod%i]);
	jc[0]=jcc[0]=1;
	for(rg int i=1;i<=mmax;i++) jc[i]=mulmod(jc[i-1],i),jcc[i]=mulmod(jcc[i-1],ny[i]);
}
int getA(rg int nn,rg int mm){
	return mulmod(jc[nn],jcc[nn-mm]);
}
struct jie{
	int id,siz;
	jie(){}
	jie(rg int aa,rg int bb){
		id=aa,siz=bb;
	}
}sta[maxn];
bool cmp(rg jie aa,rg jie bb){
	return aa.siz<bb.siz;
}
int tp;
std::vector<int> f[maxn];
void solve(rg int da,rg int l,rg int r){
	if(l==r){
		f[da].clear(),f[da].resize(2);
		f[da][0]=1,f[da][1]=sta[l].siz;
		return;
	}
	f[da].clear();
	rg int mids=(l+r)>>1,bit=0,lim=1,len=r-l+1;
	solve(da<<1,l,mids),solve(da<<1|1,mids+1,r);
	for(;lim<=len+len;lim<<=1) bit++;
	for(rg int i=0;i<lim;i++) wz[i]=(wz[i>>1]>>1)|((i&1)<<(bit-1));
	f[da].resize(lim),f[da<<1].resize(lim),f[da<<1|1].resize(lim);
	ntt(f[da<<1],lim,1),ntt(f[da<<1|1],lim,1);
	for(rg int i=0;i<lim;i++) f[da][i]=mulmod(f[da<<1][i],f[da<<1|1][i]);
	ntt(f[da],lim,-1);
	for(rg int i=len+1;i<lim;i++) f[da][i]=0;
}
int h[maxn],tot=1,ans,val[maxn],siz[maxn];
struct asd{
	int to,nxt;
}b[maxn];
void ad(rg int aa,rg int bb){
	b[tot].to=bb;
	b[tot].nxt=h[aa];
	h[aa]=tot++;
}
void mul(std::vector<int>&A,rg int xs,rg int lim){
	std::vector<int> B;
	B.resize(lim+1);
	for(rg int i=A.size();i>=1;i--) B[i]=mulmod(A[i-1],xs);
	for(rg int i=A.size()-1;i>=0;i--) B[i]=addmod(B[i],A[i]);
	A=B;
}
void div(std::vector<int>&A,rg int xs,rg int lim){
	std::vector<int> B;
	B.resize(lim-1);
	rg int ny=ksm(xs,mod-2);
	for(rg int i=A.size()-2;i>=0;i--) B[i]=mulmod(A[i+1],ny),A[i]=delmod(A[i],B[i]);
	A=B;
}
void dfs(rg int now,rg int lat){
	siz[now]=1;
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==lat) continue;
		dfs(u,now);
		siz[now]+=siz[u];
		ans=addmod(ans,mulmod(val[now],val[u]));
		val[now]=addmod(val[now],val[u]);
	}
	tp=0;
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==lat) continue;
		sta[++tp]=jie(u,siz[u]);
	}
	std::sort(sta+1,sta+1+tp,cmp);
	if(tp) solve(1,1,tp);
	else {
		f[1].clear(),f[1].push_back(1);
	}
	for(rg int i=0;i<=k && i<f[1].size();i++) val[now]=addmod(val[now],mulmod(f[1][i],getA(k,i)));
	std::vector<int> tmp;
	rg int cs=0,lst=-1;
	for(rg int i=1;i<=tp;i++){
		if(sta[i].siz!=lst){
			tmp=f[1],cs=0;
			mul(tmp,n-siz[now],tmp.size());
			div(tmp,sta[i].siz,tmp.size());
			lst=sta[i].siz;
			for(rg int j=0;j<=k && j<tmp.size();j++) cs=addmod(cs,mulmod(tmp[j],getA(k,j)));
		}
		ans=addmod(ans,mulmod(cs,val[sta[i].id]));
	}
}
int main(){
	memset(h,-1,sizeof(h));
	n=read(),k=read();
	pre1(),pre2();
	rg int aa,bb;
	for(rg int i=1;i<n;i++){
		aa=read(),bb=read();
		ad(aa,bb),ad(bb,aa);
	}
	if(k==1) ans=1LL*n*(n-1)/2LL%mod;
	else dfs(1,0);
	printf("%d\n",ans);
	return 0;
}

B. 小b爱取模

分析

首先把题目转换一下

\(b[i]\) 为原数组的差分数组

原数组变为 \(0\) 等价于差分数组变为 \(0\)

对原数组区间 \([l,r]\) 整体加 \(1\) 等价于差分数组的 \(l\) 位置加 \(1\)\(r+1\) 的位置减 \(1\)

如果 \(r=n\) 最后就不要减了

\(ans[i]\) 为一种可行方案下 \(i\) 位置的操作数

考虑每次只对于 \([i,n]\) 操作,那么差分数组中受影响的只是 \(i\) 这个位置

所以 \(ans[i]=k-b[i]\)

注意差分数组是模意义下的,但是 \(ans\) 数组不是模意义下的

这样去操作不一定是最优的,所以我们考虑把一些加的操作变成减的操作

也就是把原来区间的左端点变成了右端点 \(+1\)

在模意义下,加 \(ans[i]\) 就等于减 \(k-ans[i]\)

即把 \(ans[i]\) 变成 \(ans[i]-k\)

因为加的操作肯定要在减的操作之前

所以实质上就是在保证前缀非负的情况下减去尽可能多的 \(k\)

贪心的从大到小,从后向前减就行了

之所以从大到小是因为要让最后的答案尽量小,先把大的选走,否则有可能小的选走之后大的就不满足了

之所以从后向前是因为越靠后的位置对于前缀和的影响更小,也更容易满足

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
#include<ctime>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e7+5;
char s[maxn];
int a[maxn],k,n,sum[maxn],ans[maxn];
int main(){
	k=read();
	scanf("%s",s+1);
	n=strlen(s+1);
	for(rg int i=1;i<=n;i++) a[i]=s[i]-'0';
	for(rg int i=1;i<=n;i++) ans[i]=(a[i]-a[i-1]+k)%k;
	for(rg int i=1;i<=n;i++) ans[i]=(k-ans[i])%k;
	for(rg int i=1;i<=n;i++) sum[i]=sum[i-1]+ans[i];
	for(rg int o=k-1;o>=0;o--){
		for(rg int i=1;i<=n;i++) sum[i]=sum[i-1]+ans[i];
		rg int mmin=0x3f3f3f3f;
		for(rg int i=n;i>=1;i--){
			mmin=std::min(mmin,sum[i]);
			if(mmin>=k && ans[i]==o){
				ans[i]-=k;
				mmin-=k;
			}
		}
	}
	rg int tot=0;
	for(rg int i=1;i<=n;i++) if(ans[i]>0) tot+=ans[i];
	printf("%d\n",tot);
	return 0;
}

C. 小b爱实数

分析

\(1\) 的个数的前缀和为 \(sum\)

那么目标就是最小化 \(|f-\frac{s_i-s_j}{i-j}|\)\(|\frac{(fi-s_i)-(fj-s_j)}{i-j}|\)

发现这个式子就是斜率的形式,画个图不难发现最终构成答案的两个点的纵坐标一定是排序后相邻的

直接做就好了,复杂度 \(O(nlogn)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#define rg register
const int maxn=1e6+5;
const double eps=1e-10;
double f,ans=1e18;
char s[maxn];
int sum[maxn],jl,len;
struct asd{
	int x;
	double y;
}b[maxn];
bool cmp(rg asd aa,rg asd bb){
	return aa.y<bb.y;
}
int main(){
	scanf("%lf%s",&f,s+1);
	len=strlen(s+1);
	for(rg int i=1;i<=len;i++) sum[i]=sum[i-1]+(s[i]=='1');
	for(rg int i=1;i<=len;i++) b[i].x=i,b[i].y=sum[i]-f*i;
	std::sort(b,b+1+len,cmp);
	for(rg int i=0;i<len;i++){
		rg double tmp=std::abs((b[i+1].y-b[i].y)/(1.0*b[i+1].x-1.0*b[i].x));
		if(tmp+eps<ans){
			ans=tmp,jl=std::min(b[i].x,b[i+1].x);
		} else if(tmp<ans+eps){
			jl=std::min(jl,std::min(b[i].x,b[i+1].x));
		}
	}
	printf("%d\n",jl);
	return 0;
}
posted @ 2021-02-14 21:44  liuchanglc  阅读(49)  评论(0编辑  收藏  举报