CSP模拟58联测20

A.回忆旅途的过往

简单题。最多不超过 10 种数,直接线段树维护 bitset 表示集合种类。修改就是简单的区间覆盖,每一次询问跑一遍完全背包时间开销太大,可以预处理出来,就能做到 \(O(\log n)\) 查询。

时间复杂度 \(O(2^{10} m+n\log n)\) 赛时差点被卡常😨😨😨原因可能是预处理的时候我直接枚举所有的位数,可以改进为每次取 \(lowbit\)。不过还好卡过去了🥰

点击查看代码
#include<bits/stdc++.h>
using ll=long long;
int n,m,Q,a[1000005],b[100005],c[15],cnt;
struct offline{int opt,l,r,x;}q[1000005];
struct SegmentTree{
	struct node{std::bitset<16> s;int tag;}T[4000005];
	inline void pushup(int o){T[o].s=T[o<<1].s|T[o<<1|1].s;}
	inline void addtag(int o,int v){T[o].tag=v;T[o].s.reset();T[o].s[b[v]]=true;}
	inline void pushdown(int o){
		if(~T[o].tag){
			addtag(o<<1,T[o].tag);
			addtag(o<<1|1,T[o].tag);
			T[o].tag=-1;
		}
	}
	void build(int o,int l,int r){
		T[o].s.reset();T[o].tag=-1;
		if(l==r)return T[o].s[b[a[l]]]=true,void();
		int mid=(l+r)>>1;
		build(o<<1,l,mid);
		build(o<<1|1,mid+1,r);
		pushup(o);
	}
	void modify(int o,int l,int r,int ql,int qr,int v){
		if(ql<=l&&r<=qr)return addtag(o,v);
		pushdown(o);
		int mid=(l+r)>>1;
		if(ql<=mid)modify(o<<1,l,mid,ql,qr,v);
		if(qr>mid)modify(o<<1|1,mid+1,r,ql,qr,v);
		pushup(o);
	}
	std::bitset<16> query(int o,int l,int r,int ql,int qr){
		if(ql<=l&&r<=qr)return T[o].s;
		pushdown(o);
		int mid=(l+r)>>1;std::bitset<16> ans;
		if(ql<=mid)ans|=query(o<<1,l,mid,ql,qr);
		if(qr>mid)ans|=query(o<<1|1,mid+1,r,ql,qr);
		return ans;
	}
}t;
std::bitset<100005> f[1500];
int h[1500],hash[1500];
int maxV,tot,maxn;
inline int getid(std::bitset<16> x){
	int statu=0;
	for(int i=10;i;--i)statu=statu*2+x[i];
	return hash[statu];
}
int main(){ 
	std::cin.tie(nullptr)->sync_with_stdio(false);
	std::cin>>n>>m>>Q;
	for(int i=1;i<=n;++i){
		std::cin>>a[i];
		if(!b[a[i]])b[a[i]]=++cnt,c[cnt]=a[i];
	}
	for(int i=1;i<=Q;++i){
		std::cin>>q[i].opt>>q[i].l>>q[i].r>>q[i].x;
		if(q[i].opt==1){if(!b[q[i].x])b[q[i].x]=++cnt,c[cnt]=q[i].x;}
		else maxV=std::max(maxV,q[i].x);
	}
	maxn=1<<cnt;
	for(int S=0;S<maxn;++S){
		h[++tot]=S;
		hash[S]=tot;
	}
	for(int i=1;i<=tot;++i){
		f[i][0]=true;
		for(int k=1;k<=10;++k){
			if(!(h[i]&(1<<k-1)))continue;
			for(int j=c[k];j<=maxV;++j){
				f[i][j]=f[i][j]|f[i][j-c[k]];
			}
		}
	}
	t.build(1,1,n);
	for(int i=1;i<=Q;++i){
		if(q[i].opt==1)t.modify(1,1,n,q[i].l,q[i].r,q[i].x);
		else{
			f[getid(t.query(1,1,n,q[i].l,q[i].r))][q[i].x]?
			std::cout<<"Yes\n":std::cout<<"No\n";
		}
	}
	return 0;
}

B.牵着她的手

可以观察到序列合法的充要条件是 \(\max\{x_1...x_n\}=\max\{x_{n+1}...x_{n+m}\}\)

必要性显然,充分性可以构造一个矩阵,让那一行与列的交点填最大值,那一行与列的其他位置填任意数,其余位置看作等价的,都填 \(1\)。这样的矩阵和序列形成了双射。

然后就可以枚举最大值,不难推出答案 \(\sum_{i=1}^{k} (i^n-(i-1)^n)(i^m-(i-1)^m)\)。注意到 \(k\)\(10^9\) 级别的,不能直接枚举。又观察到这个式子是一个 \(n+m+1\) 次多项式,所以直接做拉格朗日插值,可以做到 \(O(n+m+2)\) 求出 \(f(k)\)

点击查看代码
#include<bits/stdc++.h>
using ll=long long;
constexpr int P=1e9+7;
inline ll qpow(ll a,ll b){ll ans(1);for(;b;b>>=1,a=a*a%P)if(b&1)ans=ans*a%P;return ans;}
int n,m,k,fn[200005],fm[200005],isp[200005],tot;
int g[200005],pre[200005],suf[200005],fac[200005],inv[200005];
inline void linear_sieve(int x){
	tot=0;for(int i=2;i<=x;++i)fn[i]=fm[i]=0;
	fn[0]=fm[0]=0;fn[1]=fm[1]=1;
	for(int i=2;i<=x;++i){
		if(!fn[i])fn[i]=qpow(i,n),fm[i]=qpow(i,m),isp[++tot]=i;
		for(int j=1;j<=tot&&1ll*isp[j]*i<=x;++j){
			fn[isp[j]*i]=1ll*fn[isp[j]]*fn[i]%P;
			fm[isp[j]*i]=1ll*fm[isp[j]]*fm[i]%P;
			if(i%isp[j]==0)break;
		}
	}
}
inline void prework(int x,int lim){
	linear_sieve(lim);
	for(int i=1;i<=lim;++i)g[i]=(g[i-1]+1ll*(fn[i]-fn[i-1])*(fm[i]-fm[i-1])%P)%P;
	pre[0]=suf[lim+1]=1;
	for(int i=1;i<=lim;++i)pre[i]=1ll*pre[i-1]*(x-i)%P;
	for(int i=lim;i;--i)suf[i]=1ll*suf[i+1]*(x-i)%P;
}
inline int lagrange(int x,int y){
	int ans(0);
	for(int i=1;i<=y;++i)
		ans=(ans+((y-i)&1?-1ll:1ll)*g[i]*pre[i-1]%P*suf[i+1]%P*inv[i-1]%P*inv[y-i]%P)%P;
	return (ans+P)%P;
}
inline void solve(){
	std::cin>>n>>m>>k;
	prework(k,n+m+2);
	std::cout<<lagrange(k,n+m+2)<<'\n';
}
int main(){
	std::cin.tie(nullptr)->sync_with_stdio(false);
	fac[0]=inv[0]=1;
	for(int i=1;i<=200002;++i)fac[i]=1ll*fac[i-1]*i%P;
	inv[200002]=qpow(fac[200002],P-2);
	for(int i=200001;i;--i)inv[i]=1ll*inv[i+1]*(i+1)%P;
	int T;std::cin>>T;while(T--)solve();
	return 0;
}

C.注视一切的终结

不会。

D.超越你的极限

不会。树形 dp 30pts。取前缀 \(\max\) 转移可以从 \(O(n^3)\) 优化到 \(O(n^2)\)。不过还是 30pts😞😞😞

点击查看代码
#include<bits/stdc++.h>
using ll=long long;
std::vector<std::pair<int,int>> g[1005];
int n,w[1005];
ll dp[1005][1005];
void dfs(int x,int f){
	int mn=1000;
	for(auto &y:g[x]){
		if(y.second==f)continue;
		mn=std::min(mn,y.first);
		dfs(y.second,x);
	}
	for(int i=0;i<=mn;++i){
		ll tot=0;
		for(auto &y:g[x]){
			if(y.second==f)continue;
			tot+=dp[y.second][y.first-i];
		}
		dp[x][i]=(ll)i*w[x]+tot;
	}
	for(int i=1;i<=1000;++i)dp[x][i]=std::max(dp[x][i],dp[x][i-1]);
}
int main(){
	std::cin.tie(nullptr)->sync_with_stdio(false);
	std::cin>>n;for(int i=1;i<=n;++i)std::cin>>w[i];
	for(int i=1,x,y,z;i<n;++i)std::cin>>x>>y>>z,g[x].push_back({z,y}),g[y].push_back({z,x});
	dfs(1,0);
	std::cout<<dp[1][1000]<<std::endl;
	return 0;
}

posted @ 2023-10-19 07:26  Flandreqwq  阅读(15)  评论(0编辑  收藏  举报