20190805校内模拟题解

5 篇文章 0 订阅
2 篇文章 0 订阅

T1:树上选一个包含根的连通块,求价格在给定范围内的最大价值(背包)
n<=5e3,背包容量<=1e4
SOL:任轩笛在2018年国集论文中写到的关于树上连通块的一个重要性质:dfs序的转移
具体的,包含根的连通块可以这样表示:
设当前点为v,dfs序为dfs[v],构造一个新图
dfs[v]向dfs[v]+1连边,表示选择这个点,其他点任选
dfs[v]向dfs[v]+siz[v]连边,表示不选这个点,那么这个点的子树也不能选
那么新图中一条1到n+1的路径就表示一种包含1的连通块的选法,新图是一个DAG
那么直接在DAG上按照拓扑序背包一下就完了

Code:

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define mp make_pair
#define db double
#define ri register
#define fi first
#define se second
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
const int INF=1e9,N=1e4+5,M=1e4+5;
struct E{
	int vis[N],head[N],nxt[N],v[N],w[N],tot;
	E(){tot=0;}
	inline void add(int x,int y,int z,int ww){vis[++tot]=y;nxt[tot]=head[x];head[x]=tot;v[tot]=z;w[tot]=ww;}
}tr,gr;
int dfn[N],siz[N],sign=0;
int in[N];
void dfs(int v){
	dfn[v]=++sign;siz[v]=1;
	for(int i=tr.head[v];i;i=tr.nxt[i]){
		int y=tr.vis[i];
		dfs(y);
		siz[v]+=siz[y];
	}
}
int dp[5005][M];
int n,p;
int w[N],v[N];
inline void build(){
	for(ri int i=1;i<=n;++i){
		gr.add(dfn[i],dfn[i]+siz[i],0,0);++in[dfn[i]+siz[i]];
		gr.add(dfn[i],dfn[i]+1,v[i],w[i]);++in[dfn[i]+1];
	}
}
queue<int>q;
inline void DP(){
	q.push(1);
	memset(dp,0,sizeof(dp));
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=gr.head[x];i;i=gr.nxt[i]){
			int y=gr.vis[i],W=gr.w[i],V=gr.v[i];--in[y];
			for(ri int j=0;j<=p;++j){
				if(x!=1 && !dp[x][j]) continue;
				if(j+W<=p) dp[y][j+W]=max(dp[y][j+W],dp[x][j]+V);
			}
			if(!in[y]) q.push(y);
		}
	}
	int ans=0;
	for(int i=0;i<=p;i++) ans=max(ans,dp[n+1][i]);
	cout<<ans;
}
inline void file(){freopen("medicine.in","r",stdin);freopen("medicine.out","w",stdout);}
int main(){
	n=read();p=read();
	for(ri int i=1;i<=n;++i){
		w[i]=read();int fa=read();v[i]=read();
		if(i!=1) tr.add(fa,i,0,0);
	}
	dfs(1);build();DP();
	return 0;
}

T2:有一棵splay,需要支持三个操作:
1.左旋x
2.右旋x
3.询问x的子树中,所有点的子树权值和之积
n,q<=200000
SOL:简单分析一下,左旋和右旋可以用LCT维护,link和cut会影响的就是当前点到根这条路径的值,会让路径上所有值乘上或除以(乘逆元)一个数,那就是链加,单点询问,LCT做就完了,细节有点多

Code:

#include<bits/stdc++.h>
#define mod 1000000007
#define ll long long
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
inline int add(int x,int y){x+=y;if(x>=mod) x-=mod;return x;}
inline int mul(int x,int y){return (ll)x*y%mod;}
inline int ksm(int a,int b){int res=1;for(;b;b>>=1,a=mul(a,a)) if(b&1) res=mul(res,a);return res;}
const int N=2e5+5;
namespace LCT{
	int lc[N],rc[N];
	int w[N],sum[N],ans[N];
	int ls[N],rs[N],fa[N],ml[N],f[N];
	int que[N];
	inline int isrs(int x){return rs[fa[x]]==x;}
	inline bool isroot(int x){return ls[fa[x]]!=x && rs[fa[x]]!=x;}
	inline void pushmul(int x,int v){
		ans[x]=mul(ans[x],v);
		ml[x]=mul(ml[x],v);
	}
	inline void pushdown(int x){
		if(ml[x]!=1){
			if(ls[x]) pushmul(ls[x],ml[x]);
			if(rs[x]) pushmul(rs[x],ml[x]);
			ml[x]=1;
		}
	}
	inline void rotate(int x){
		int y=fa[x],z=fa[y],b=ls[y]==x?rs[x]:ls[x];
		if(z && !isroot(y)) (ls[z]==y?ls[z]:rs[z])=x;
		fa[x]=z;fa[y]=x;b?fa[b]=y:0;
		if(ls[y]==x) rs[x]=y,ls[y]=b;
		else ls[x]=y,rs[y]=b;
	}
	inline void splay(int x){
		que[que[0]=1]=x;
		for(int y=x;!isroot(y);y=fa[y]) que[++que[0]]=fa[y];
		for(int i=que[0];i;i--) pushdown(que[i]);
		while(!isroot(x)){
			if(!isroot(fa[x])){
				if(isrs(fa[x])==isrs(x)) rotate(fa[x]);
				else rotate(x);
			}
			rotate(x);
		}
	}
	inline void access(int x){for(int y=0;x;y=x,x=fa[x]){splay(x);rs[x]=y;}}
	inline void link(int x,int y){access(x);splay(x);fa[x]=y;}
	inline void cut(int x,int y){access(y);splay(x);fa[x]=0;}
	inline void push(int x,int v){access(x);splay(x);pushmul(x,v);}
	inline void init(int n){
		sum[0]=sum[n+1]=0;w[n+1]=ans[n+1]=ml[n+1]=ans[0]=ml[0]=1;
		for(int i=1;i<=n;i++){
			w[i]=read();lc[i]=read();rc[i]=read();
			if(lc[i]) f[lc[i]]=fa[lc[i]]=i;
			if(rc[i]) f[rc[i]]=fa[rc[i]]=i;
			ml[i]=1;
		}
		f[1]=fa[1]=n+1;lc[n+1]=1;
		for(int i=n;i;i--){
			sum[i]=add(w[i],add(sum[lc[i]],sum[rc[i]]));
			ans[i]=mul(sum[i],mul(ans[lc[i]],ans[rc[i]]));
		}
		sum[n+1]=add(w[n+1],add(sum[lc[n+1]],sum[rc[n+1]]));
		ans[n+1]=mul(sum[n+1],mul(ans[lc[n+1]],ans[rc[n+1]]));
	}
	inline int ask(int x){if(!x) return 1;splay(x);return ans[x];}
}
using namespace LCT;
inline void zig(int x){
	if(!lc[x]) return;
	int y=lc[x],p=f[x];
	int inv=ksm(sum[y],mod-2);
	cut(x,p);cut(y,x);
	if(rc[y]) cut(rc[y],y),link(rc[y],x);
	link(x,y);link(y,p);
	(rc[p]==x?rc[p]:lc[p])=y;
	f[y]=p,f[x]=y;if(rc[y]) f[rc[y]]=x;
	lc[x]=rc[y],rc[y]=x;
	sum[y]=sum[x];
	sum[x]=add(w[x],add(sum[lc[x]],sum[rc[x]]));
	inv=mul(inv,sum[x]);
	push(p,inv);
	ans[x]=mul(sum[x],mul(ask(lc[x]),ask(rc[x])));
	ans[y]=mul(sum[y],mul(ask(lc[y]),ask(rc[y])));
}
inline void zag(int x){
	if(!rc[x]) return;
	int y=rc[x],p=f[x];
	int inv=ksm(sum[y],mod-2);
	cut(x,p);cut(y,x);
	if(lc[y]) cut(lc[y],y),link(lc[y],x);
	link(x,y);link(y,p);
	(rc[p]==x?rc[p]:lc[p])=y;
	f[y]=p,f[x]=y;if(lc[y]) f[lc[y]]=x;
	rc[x]=lc[y],lc[y]=x;
	sum[y]=sum[x];
	sum[x]=add(w[x],add(sum[lc[x]],sum[rc[x]]));
	inv=mul(inv,sum[x]);
	push(p,inv);
	ans[x]=mul(sum[x],mul(ask(lc[x]),ask(rc[x])));
	ans[y]=mul(sum[y],mul(ask(lc[y]),ask(rc[y])));
}
int main(){
	int n=read(),q=read();
	init(n);
	while(q--){
		switch(read()){
			case 0:zig(read());break;
			case 1:zag(read());break;
			case 2:cout<<ask(read())<<"\n";
		}
	}
	return 0;		
}

T3:一个网格,你在(0,0),每次可以走到一个以当前点位右下角的mx,my的矩形内,但是不能走到k个位置,这k个位置与当前点的相对距离都是 ( k i , k i ) (k_i,k_i) (ki,ki),且 k i k_i ki是10的倍数,求r步走到目标点的方案数
r<=1000,坐标<=500,k<=50
SOL:dp+容斥,两个维度分开考虑,最后乘起来,不能走的点用组合数+容斥处理掉
Code:

#include<bits/stdc++.h>
#define mod 10007
#define ll long long
#define pb push_back
using namespace std;
inline int read(){
	int res=0,f=1;char ch=getchar();
	while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
	return res*f;
}
inline int add(int x,int y){x+=y;if(x>=mod) x-=mod;return x;}
inline int dec(int x,int y){x-=y;if(x<0) x+=mod;return x;} 
inline int mul(int x,int y){return (ll)x*y%mod;}
inline void inc(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline void Dec(int &x,int y){x-=y;if(x<0) x+=mod;}
inline int ksm(int a,int b){int res=1;for(;b;b>>=1,a=mul(a,a)) if(b&1) res=mul(res,a);return res;}
const int N=1700,M=900;
int fx[N][M],fy[N][M];
int g[N][M/10];
int fac[N],ifac[N];
inline void init(int n){
	fac[0]=1;ifac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=mul(fac[i-1],i);
	ifac[n]=ksm(fac[n],mod-2);
	for(int i=n-1;i;i--) ifac[i]=mul(ifac[i+1],i+1);
}
inline int C(int n,int m){if(n<0 && m<0 && n<m) return 0;return mul(fac[n],mul(ifac[m],ifac[n-m]));}
inline int calc(int i,int x,int y){return mul(dec(fx[i][x+1],fx[i][x]),dec(fy[i][y+1],fy[i][y]));}
vector<int>ban;
int main(){
	int tx=read(),ty=read(),mx=read(),my=read(),r=read(),k=read();
	for(int i=1;i<=k;i++) ban.pb(read());
	init(r);
	int lim=min(tx,ty)/10;
	g[0][0]=1;ban.pb(0);
	for(int i=0;i<ban.size();i++) ban[i]/=10;
	for(int i=1;i<=r;i++) for(int j=0;j<ban.size();j++) for(int k=lim;k>=ban[j];k--) inc(g[i][k],g[i-1][k-ban[j]]);
	for(int i=1;i<=tx+1;i++) fx[0][i]=1;
	for(int i=1;i<=r;i++) for(int j=1;j<=tx+1;j++) fx[i][j]=add(fx[i][j-1],dec(fx[i-1][j],fx[i-1][j-min(j-1,mx)-1]));
	for(int i=1;i<=ty+1;i++) fy[0][i]=1;
	for(int i=1;i<=r;i++) for(int j=1;j<=ty+1;j++) fy[i][j]=add(fy[i][j-1],dec(fy[i-1][j],fy[i-1][j-min(j-1,my)-1]));
	int ans=0;
	for(int i=0;i<=r;i++){
		int op=(i&1)?mod-C(r,i):C(r,i);
		for(int j=0;j<=lim;j++) inc(ans,mul(op,mul(g[i][j],calc(r-i,tx-j*10,ty-j*10))));
	}
	cout<<ans;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值