2021.07.07【NOIP提高B组】模拟 总结

2021.07.07【NOIP提高B组】模拟 总结

第一题:首先预处理ci,jc_{i,j}ci,j表示第iii行选了jjj个的最大价值,这个可以用暴力或者动态规划求出。然后设fi,jf_{i,j}fi,j表示前iii行选了jjj个的答案,转移枚举kkkkkk转移到jjj,注意优化。

第二题:考虑动态规划,设fi,jf_{i,j}fi,j表示前iii种颜色其中有jjj对颜色相邻且相等,考虑第i+1i+1i+1种颜色。设sis_isi表示前iii种颜色的个数和,aia_iai表示第iii种颜色的个数。把这种颜色分为kkk块,其中有l(l≤k,j)l(l\le k,j)l(lk,j)块插入到了原来相邻颜色中。有以下选择:

  • 从这ai+1a_{i+1}ai+1个颜色中分成kkk组,插板问题,为Cai+1−1k−1C_{a_{i+1}-1}^{k-1}Cai+11k1
  • 从这jjj对相邻的颜色中选择lll组,为CjlC_j^lCjl
  • 把剩下的k−lk-lkl个插入在其他位置,其他位置有si−j+1s_i-j+1sij+1(加上111是因为第一个位置前也可以插入),为Csi−j+1k−lC_{s_i-j+1}^{k-l}Csij+1kl

那么新的有多少个相邻颜色呢?

可以发现为j−l+ai+1−kj-l+a_{i+1}-kjl+ai+1k。那么可得fi+1,j−l+ai+1−k=fi,j×Cai+1−1k−1×Cjl×Csi−j+1k−lf_{i+1,j-l+a_{i+1}-k}=f_{i,j}\times C_{a_{i+1}-1}^{k-1}\times C_j^l\times C_{s_i-j+1}^{k-l}fi+1,jl+ai+1k=fi,j×Cai+11k1×Cjl×Csij+1kl

预处理组合数就可以了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
int t,n,a[20],s[20];
ll f[20][100],c[100][100];
const int mod=1000000007;
inline ll C(register int x,register int y){
	return c[x][y];
}
inline ll inv(register ll x){
	register int y=mod-2;
	register ll sum=1;
	while (y){
		if (y&1) sum*=x,sum%=mod;
		x*=x,x%=mod;
		y>>=1;
	}
	return sum;
}
int main(){
	freopen("whitewasher.in","r",stdin);
	freopen("whitewasher.out","w",stdout);
	scanf("%d",&t);
	c[0][0]=1;
	for (register int i=1;i<=100;i++){
		c[i][0]=1;
		for (register int j=1;j<=i;j++){
			c[i][j]=c[i-1][j-1]+c[i-1][j];
			c[i][j]%=mod;
		}
	}
	while (t--){
		scanf("%d",&n);
		for (register int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			s[i]=s[i-1]+a[i];
		}
		memset(f,0,sizeof(f));
		f[0][0]=1;
		for (register int i=1;i<=n;i++){
			for (register int j=0;j<=min(s[i-1]+1,s[i]);j++){
				for (register int k=1;k<=a[i];k++){
					for (register int l=0;l<=min(j,min(k,j+a[i]-k));l++){
						f[i][j+a[i]-k-l]+=((((f[i-1][j]*C(a[i]-1,k-1))%mod*C(j,l))%mod*C(s[i-1]+1-j,k-l))%mod);
						f[i][j+a[i]-k-l]%=mod;
					}
				}
			}
		}
		printf("%lld\n",f[n][0]);
	}
}

第三题:设fi,sf_{i,s}fi,s表示当前树根为iii,状态为jjj的最小代价。设disi,jdis_{i,j}disi,j为输入的距离。

明显的转移:fi,s=fj,s′+fi,s−s′+disi,j(s′⊂s)f_{i,s}=f_{j,s'}+f_{i,s-s'}+dis_{i,j}(s'\subset s)fi,s=fj,s+fi,ss+disi,j(ss)

还有就是fi,s=fj,s+disi,jf_{i,s}=f_{j,s}+dis_{i,j}fi,s=fj,s+disi,j

由于上面这个式子无法知道顺序,所以做一遍最短路。

然后我们只要把fff给合并起来,注意要保证iiin−i+1n-i+1ni+1必须同时在状态中或者同时不在,因为不在的话说明他们没有连通。

#include<bits/stdc++.h>
using namespace std;
int n,m,k,la[10005],to[20005],ne[20005],we[20005],cnt,f[500][10005],dis[10005],vis[10005];
int g[500],h[500];
const int inf=1000000000;
void add(int x,int y,int z){
	++cnt;
	to[cnt]=y;
	ne[cnt]=la[x];
	we[cnt]=z;
	la[x]=cnt;
}
queue<int>q,q2;
void spfa(int st){
	q=q2;
	for (int i=1;i<=n;i++){
		dis[i]=f[st][i];
		vis[i]=0;
		q.push(i);
	}
	while (!q.empty()){
		int x=q.front();
		vis[x]=0;
		q.pop();
		for (int i=la[x];i;i=ne[i]){
			int y=to[i];
			if (dis[x]+we[i]<dis[y]){
				dis[y]=dis[x]+we[i];
				if (!vis[y]){
					vis[y]=1;
					q.push(y);
				}
			}
		} 
	}
}
bool check(int x){
	for (int i=0;i<k;i++){
		int f1=(1<<i)&x;
		int f2=(1<<(i+k))&x;
		if (f1>0&&f2==0) return 0;
		if (f1==0&&f2>0) return 0;
	}
	return 1;
}
int main(){
	freopen("travel.in","r",stdin);
	freopen("travel.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	for (int i=1,x,y,z;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	for (int i=0;i<(1<<(2*k));i++)
		for (int j=1;j<=n;j++)
			f[i][j]=inf;
	for (int i=k+1;i<=n-k;i++) f[0][i]=0;
	for (int i=1;i<=k;i++){
		f[1<<(i-1)][i]=0;
		f[1<<(k+i-1)][n-i+1]=0;
	}
	for (int i=1;i<(1<<(2*k));i++){
		for (int j=1;j<=n;j++){
			for (int i1=i;i1;i1=(i1-1)&i){
				if (f[i1][j]>=f[i][j]) continue; 
				if (i1!=i){
					for (int j1=la[j];j1;j1=ne[j1]){
						int j2=to[j1],j3=we[j1];
						f[i][j]=min(f[i][j],f[i1][j]+f[i-i1][j2]+j3);
					}
				}
			} 
		}
		spfa(i);
		g[i]=inf;
		for (int j=1;j<=n;j++) f[i][j]=dis[j],g[i]=min(g[i],f[i][j]);
	}
	for (int i=1;i<(1<<(2*k));i++)h[i]=inf;
	h[0]=0;
	for (int i=1;i<(1<<(2*k));i++)
		if (check(i))
			for (int j=i;j;j=(j-1)&i)
				if (check(i-j))
					h[i]=min(h[i],h[i-j]+g[j]);
	if (h[(1<<(2*k))-1]==inf) printf("-1");
	else printf("%d",h[(1<<(2*k))-1]);
}

第四题:看到数据先离散化。考虑每个点上建一个线段树维护答案,那么就可以用差分修改答案,对于(x,y)(x,y)(x,y)以及lca(x,y)lca(x,y)lca(x,y)fa(lca(x,y))fa(lca(x,y))fa(lca(x,y)),将前两个点加一,后两个点减一。但是这样空间肯定回报,考虑动态开点。差分之后要统计,统计时将线段树合并。所以用线段树合并的方法。注意一些细节。当然可以用树链剖分做。

#include<bits/stdc++.h>
using namespace std;
int n,m,la[100005],to[200005],ne[200005],cnt;
int a[100005],b[100005],c[100005],dep[100005],f[100005][17];
int t[2005][2005],drk[100005],rt[100005],ans[100005];
const int inf=1000000000;
struct food{
	int val,id,rk;
}d[100005];
struct tree{
	int val,ls,rs,id;
}tr[100005*80];
int sz=0;
void pushup(int x){
	int ls=tr[x].ls;
	int rs=tr[x].rs;
	if (tr[ls].val>=tr[rs].val){
		tr[x].val=tr[ls].val;
		tr[x].id=tr[ls].id;
	}
	else{
		tr[x].val=tr[rs].val;
		tr[x].id=tr[rs].id;
	}
}
void update(int &x,int l,int r,int k,int v){
	if (!x) x=++sz;
	if (l==r&&l==k){
		tr[x].id=k;
		tr[x].val+=v;
		return;
	}
	int mid=(l+r)>>1;
	if (k<=mid) update(tr[x].ls,l,mid,k,v);
	else update(tr[x].rs,mid+1,r,k,v);
	pushup(x);
}
void merge(int &x,int y,int l,int r){
	if ((!x)||(!y)) x+=y;
	else if (l==r) tr[x].val+=tr[y].val;
	else{
		int mid=(l+r)>>1;
		merge(tr[x].ls,tr[y].ls,l,mid);
		merge(tr[x].rs,tr[y].rs,mid+1,r);
		pushup(x); 
	}
}
void add(int x,int y){
	++cnt;
	to[cnt]=y;
	ne[cnt]=la[x];
	la[x]=cnt;
}
bool cmp(food x,food y){
	return x.val<y.val;
}
void dfs(int x,int y){
	dep[x]=dep[y]+1;
	f[x][0]=y;
	for (int i=1;i<=16;i++) f[x][i]=f[f[x][i-1]][i-1];
	for (int i=la[x];i;i=ne[i])
		if (y!=to[i])
			dfs(to[i],x);
}
void dfs2(int x,int y){
	for (int i=la[x];i;i=ne[i]){
		if (y!=to[i]){
			dfs2(to[i],x);
			merge(rt[x],rt[to[i]],1,d[m].rk);
		}
	}
	if (tr[rt[x]].val) ans[x]=tr[rt[x]].id;
}
int lca(int x,int y){
	if (x==y) return x;
	if (dep[x]<dep[y]) swap(x,y);
	int k=dep[x]-dep[y],s=0;
	while (k){
		if (k&1) x=f[x][s];
		s++;
		k>>=1;
	}
	if (x==y) return x;
	for (int i=16;i>=0;i--)
		if (f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1,x,y;i<n;i++){
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(1,0);
	for (int i=1;i<=m;i++){
		scanf("%d%d%d",&a[i],&b[i],&c[i]);
		d[i].val=c[i];
		d[i].id=i;
	}
	sort(d+1,d+m+1,cmp);
	d[0].val=-1;
	for (int i=1;i<=m;i++) 
		if (d[i].val!=d[i-1].val) d[i].rk=d[i-1].rk+1;
		else d[i].rk=d[i-1].rk;
	for (int i=1;i<=m;i++){
		int x=d[i].id;
		drk[d[i].rk]=d[i].val;
		c[x]=d[i].rk;
	}
	for (int i=1;i<=m;i++){
		int x=lca(a[i],b[i]);
		update(rt[a[i]],1,d[m].rk,c[i],1);
		update(rt[b[i]],1,d[m].rk,c[i],1);
		update(rt[x],1,d[m].rk,c[i],-1);
		if (f[x][0])
			update(rt[f[x][0]],1,d[m].rk,c[i],-1);
	}
	dfs2(1,0);
	for (int i=1;i<=n;i++){
		printf("%d\n",drk[ans[i]]);
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值