2019.01.26【NOIP提高组】模拟 A&B 组

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sugar_free_mint/article/details/86660512

前言

由于A&B组题目相同,所以说一起写,但是貌似……A组第三题不会做


B组部分

JZOJ 1252 洛谷 5194 天平

题目

n n 个数中选择若干个,使它们的和不超过 c c 且最大


分析

倒序dfs,并用前缀和优化


代码

#include <cstdio>
#include <algorithm>
#define rr register
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
int n; long long c,a[41],s[41],ans;
inline void dfs(int dep,long long sum){
	if (s[dep-1]<=c-sum) ans=max(ans,s[dep-1]+sum);
	else{
		ans=max(ans,sum);
		for (rr int i=dep-1;i;--i)
		if (c-sum>=a[i]) dfs(i,sum+a[i]);
	}
}
signed main(){
	scanf("%d%lld",&n,&c);
	for (rr int i=1;i<=n;++i){
		scanf("%lld",&a[i]);
		if (a[i]>c) i--,n--;
		else s[i]=s[i-1]+a[i];
	}
	dfs(n+1,0); 
	return !printf("%lld",ans);
}

JZOJ 1274 游历路线

题目

从1号点到 n n 号点,不能停留,在每一个周期每条单向路有一定的费用,问到第 m m 天的最小费用


代码(线性动态规划)

#include <cstdio>
#include <cstring>
#include <cctype>
#define rr register
using namespace std;
int n,m,w[101][101][21],dp[201][101];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline signed min(int a,int b){return (a<b)?a:b;}
signed main(){
	freopen("lines.in","r",stdin);
	freopen("lines.out","w",stdout);
	n=iut(); m=iut();
	memset(w,127/3,sizeof(w));
	for (rr int i=1;i<=n;++i)
	for (rr int j=1;j<=n;++j) if (i!=j){
		w[i][j][0]=iut();
		for (rr int k=1;k<=w[i][j][0];++k){
		    w[i][j][k]=iut();
		    if (!w[i][j][k]) w[i][j][k]=707406378;
		}
	}
	memset(dp,127/3,sizeof(dp)); dp[0][1]=0;
	for (rr int i=1;i<=m;++i)
	for (rr int j=1;j<=n;++j) if (dp[i-1][j]!=707406378)
	for (rr int now=1;now<=n;++now) if (j!=now)
		dp[i][now]=min(dp[i][now],dp[i-1][j]+w[j][now][(i-1)%w[j][now][0]+1]);
	if (dp[m][n]!=707406378) printf("%d",dp[m][n]); else putchar(48);
	return 0;
} 

A&B组部分

JZOJ 4224 食物

题目

在这里插入图片描述


分析

那么只要美味度满足要求的食物大小总和可以用这些工具装好,那么计算工具的最小费用即为答案,为什么呢,因为食物可以拆开,但是不满足部分背包
所以这道题可以分成两部分

  1. 找到满足美味度的最小的空间
  2. 求出工具的最小费用

这些都可以通过多重背包(二进制拆分变为01背包)实现,但是问题是直接维护空间很难完成第二步,于是可以玄学的把它反转,设 f [ j ] f[j] 表示空间为 j j 的最大美味度,那么容易得到 f [ j ] = f [ j w [ i ] ] + c [ i ] , c [ i ] w [ i ] f[j]=f[j-w[i]]+c[i],c[i]表示美味度,w[i]表示空间 ,然后只要 f [ j ] p f[j]\geq p 那么对 j j 取最小值,然后取到这个最小值再用一遍多重背包求得答案


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
const int M=50000; int f[M+105],dp[M+5];
struct rec{int w,c;}a[1201],b[1201];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline void min(int &a,int b){if (a>b) a=b;}
inline void max(int &a,int b){if (a<b) a=b;}
signed main(){
	for (rr int t=iut();t;--t){
		rr int n1=iut(),n2=iut(),p=iut(),i1,poi=M+1,ans=M+1;
		for (i1=n1,n1=0;i1;--i1){//二进制拆分
		    rr int w=iut(),c=iut(),t=iut();
		    for (rr int j=1;t>=j;t-=j,j<<=1)
		    	a[++n1]=(rec){w*j,c*j};
		    if (t) a[++n1]=(rec){w*t,c*t};
		}
		for (i1=n2,n2=0;i1;--i1){
		    rr int c=iut(),w=iut(),t=iut();
		    for (rr int j=1;t>=j;t-=j,j<<=1)
		    	b[++n2]=(rec){w*j,c*j};
		    if (t) b[++n2]=(rec){w*t,c*t};
		}
		fill(f+1,f+M+101,100001); f[0]=0;
		for (rr int i=1;i<=n1;++i)
		for (rr int j=M+100;j>=a[i].w;--j){//可能还会超过一点点
		    min(f[j],f[j-a[i].w]+a[i].c);
		    if (j>=p) min(poi,f[j]);
		}
		fill(dp+1,dp+M+1,-100001); dp[0]=0;
		for (rr int i=1;i<=n2;++i)
		for (rr int j=ans;j>=b[i].w;--j){
		    max(dp[j],dp[j-b[i].w]+b[i].c);
		    if (dp[j]>=poi) min(ans,j);
		}
	    if (ans>M) printf("TAT\n"); else printf("%d\n",ans);
	}
	return 0;
} 

A组部分

JZOJ 4223 旅游

题目

询问有多少个无序点对 ( x , y ) (x,y) 至少有一条最大边权不超过 d d 的路径


分析

那么这道题适合离线处理,用并查集维护连通块的大小,从小到大排序边权,从小到大排序 d d ,然后对于每一个询问,多出来的就是 ( s o n [ x ] + s o n [ y ] ) × ( s o n [ x ] + s o n [ y ] 1 ) ( s o n [ x ] × ( s o n [ x 1 ] ) + s o n [ y ] × ( s o n [ y ] 1 ) ) (son[x]+son[y])\times(son[x]+son[y]-1)-(son[x]\times (son[x-1])+son[y]\times (son[y]-1))


代码

#include <cstdio>
#include <cctype>
#include <algorithm>
#define rr register
using namespace std;
struct node{int x,y,w;}e[100001];
struct rec{int w,rk;}a[5001];
int n,m,q,f[20001],son[20001],ans[5001];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline void print(int ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
inline signed getf(int u){return (f[u]==u)?u:f[u]=getf(f[u]);}
bool cmp1(node a,node b){return a.w<b.w;}
bool cmp2(rec x,rec y){return x.w<y.w;}
signed main(){
	for (rr int t=iut();t;--t){
		n=iut(); m=iut(); q=iut();
		for (rr int i=1;i<=m;++i) e[i]=(node){iut(),iut(),iut()};
		for (rr int i=1;i<=n;++i) son[f[i]=i]=1;
		for (rr int i=1;i<=q;++i) a[i]=(rec){iut(),i};
		sort(e+1,e+1+m,cmp1); sort(a+1,a+1+q,cmp2); rr int j=1;
		for (rr int i=1;i<=q;++i){
			ans[a[i].rk]=ans[a[i-1].rk];
			while (j<=m&&e[j].w<=a[i].w){
				rr int fa=getf(e[j].x),fb=getf(e[j].y);
				if (fa!=fb){
					ans[a[i].rk]-=son[fa]*(son[fa]-1)+son[fb]*(son[fb]-1);
					if (fa>fb) fa^=fb,fb^=fa,fa^=fb;
					son[f[fa]=fb]+=son[fa];
					ans[a[i].rk]+=son[fb]*(son[fb]-1);
				}
				++j;
			}
		}
		for (rr int i=1;i<=q;++i) print(ans[i]),putchar(10);
	}
	return 0;
}

JZOJ 4225 宝藏

题目(简化版)

询问树上一个点到另外一个点的期望长度
JZOJ 5814是这道题的弱化版


分析

终于改A了,感谢纪中YYTSSL_WHF的帮助
首先呢,两点之间距离需要用LCA实现,所以说首先要求一次LCA,然后再想期望步数,应该是要用树形dp的了
d p 1 [ x ] dp1[x] 表示从 x x 到根节点的期望步数, d p 2 [ x ] dp2[x] 表示从根节点到 x x 的期望步数,但是有点难,那首先从父节点开始,按照YYT的方法,然后用前缀和求得两个数组,然后最后对于两个相邻的 x , y x,y ,答案就是 d p 1 [ x ] d p 1 [ l c a ] + d p 2 [ y ] d p 2 [ l c a ] dp1[x]-dp1[lca]+dp2[y]-dp2[lca]
首先 d p 1 [ x ] dp1&#x27;[x] 表示从 x x 到父节点的期望步数, d p 2 [ x ] dp2&#x27;[x] 表示父节点到 x x 的期望步数
d p 1 [ x ] = 1 d e g [ x ] ( ) + ( i s o n x 1 + d p 1 [ i ] + d p 1 [ x ] ( ) ) ÷ d e g [ x ] dp1&#x27;[x]=\frac{1}{deg[x](度数)}+(\sum_{i\in son_x}1+dp1&#x27;[i]+dp1&#x27;[x](到儿子再到自己再到父节点))\div deg[x]
尝试化简这个式子得到
d p 1 [ x ] = 1 d e g [ x ] + d e g [ x ] 1 ( ) d e g [ x ] + i s o n x d p 1 [ i ] ÷ d e g [ x ] + ( d e g [ x ] 1 ) d p 1 [ x ] d e g [ x ] dp1&#x27;[x]=\frac{1}{deg[x]}+\frac{deg[x]-1(子节点个数)}{deg[x]}+\sum_{i\in son_x}dp1&#x27;[i]\div deg[x]+\frac{(deg[x]-1)dp1&#x27;[x]}{deg[x]}
把最后一项移项得到
d p 1 [ x ] d e g [ x ] = 1 + i s o n x d p 1 [ i ] ÷ d e g [ x ] \frac{dp1&#x27;[x]}{deg[x]}=1+\sum_{i\in son_x}dp1&#x27;[i]\div deg[x]
方程两边同乘 d e g [ x ] deg[x] 得到最后结果
d p 1 [ x ] = d e g [ x ] + i s o n x d p 1 [ i ] dp1&#x27;[x]=deg[x]+\sum_{i\in son_x}dp1&#x27;[i]
同样, d p 2 [ x ] = 1 d e g [ f a ] + ( i x 1 + d p 1 [ i ] + d p 2 [ x ] ( x ) ) ÷ d e g [ f a ] + 1 + d p 2 [ f a ] d e g [ f a ] dp2&#x27;[x]=\frac{1}{deg[fa]}+(\sum_{i\in x的兄弟}1+dp1&#x27;[i]+dp2&#x27;[x](到父亲再到兄弟再回去再回到x))\div {deg[fa]}+\frac{1+dp2&#x27;[fa]到父亲再回来}{deg[fa]}
化简方法差不多,那么就可以得到
d p 2 [ x ] = d e g [ f a ] + i x d p 1 [ i ] + d p 2 [ f a ] dp2&#x27;[x]=deg[fa]+\sum_{i\in x的兄弟}dp1&#x27;[i]+dp2&#x27;[fa]
但是问题是中间这一坨很难处理,没关系,这个东西就是
d p 2 [ x ] = d e g [ f a ] + d p 1 [ f a ] d p 1 [ x ] + d p 2 [ f a ] dp2&#x27;[x]=deg[fa]+dp1&#x27;[fa]-dp1&#x27;[x]+dp2&#x27;[fa]
被带偏了吧,没错,我那时被带偏了
观察 d p 1 [ f a ] = d e g [ f a ] + i s o n x d p 1 [ i ] dp1&#x27;[fa]=deg[fa]+\sum_{i\in son_x}dp1&#x27;[i]
它多出来了一个 d e g [ f a ] deg[fa] ,那就正好了,那么
d p 2 [ x ] = d p 1 [ f a ] d p 1 [ x ] + d p 2 [ f a ] dp2&#x27;[x]=dp1&#x27;[fa]-dp1&#x27;[x]+dp2&#x27;[fa]
证明完这些,其实已经比较容易打出来了


代码

#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
const int N=50010;
struct node{int y,next;}e[N<<1];
int ls[N],dep[N],dp1[N],dp2[N],deg[N],f[N][16],n,k;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline void print(int ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
inline void add(int x,int y){
	e[++k]=(node){y,ls[x]}; ls[x]=k; ++deg[x];
	e[++k]=(node){x,ls[y]}; ls[y]=k; ++deg[y];
}
inline void dfs1(int x,int fa){//dp1
	dep[x]=dep[fa]+1; dp1[x]=deg[x];
	for (rr int i=ls[x];i;i=e[i].next)
	if (e[i].y!=fa){
		f[e[i].y][0]=x;
		dfs1(e[i].y,x);
		dp1[x]+=dp1[e[i].y];
	}
}
inline void dfs2(int x,int fa){//dp2
	for (rr int i=ls[x];i;i=e[i].next)
	if (e[i].y!=fa){
		dp2[e[i].y]=dp1[x]-dp1[e[i].y]+dp2[x];
		dfs2(e[i].y,x);
	}
}
inline void dfs3(int x,int fa){//处理前缀和
    for (rr int i=ls[x];i;i=e[i].next) if (e[i].y!=fa)
	    dp1[e[i].y]+=dp1[x],dp2[e[i].y]+=dp2[x],dfs3(e[i].y,x);
}
inline signed lca(int x,int y){//求两点间的lca
	if (dep[x]<dep[y]) x^=y,y^=x,x^=y;
	for (rr int i=15;i>=0;--i) if (dep[x]-(1<<i)>=dep[y]) x=f[x][i];
	for (rr int i=15;i>=0;--i) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	if (x==y) return x; else return f[x][0];
}
signed main(){
	for (rr int t=iut();t;--t){
		memset(deg,0,sizeof(deg));
		memset(ls,0,sizeof(ls));
		memset(dp1,0,sizeof(dp1));
		memset(dp2,0,sizeof(dp2));
		n=iut(); k=1; f[1][0]=0;
		for (rr int i=1;i<n;++i) add(iut()+1,iut()+1);
		dfs1(1,0);  dfs2(1,0); dp1[1]=0;dp2[1]=0; dfs3(1,0);
		for (rr int j=1;j<16;++j)//树上倍增
		for (rr int i=1;i<=n;++i)
		f[i][j]=f[f[i][j-1]][j-1];
		for (rr int q=iut();q;--q){
			rr int len=iut()+1,ls=iut()+1,ans=0;
			while (--len){
				rr int x=iut()+1,t=lca(x,ls);
				ans+=dp1[ls]-dp1[t]+dp2[x]-dp2[t];
				ls=x;
			}
			print(ans); printf(".0000\n"); //必然是一个整数
		}
		if (t>1) putchar(10);
	}
	return 0;
} 

后续

我明明菜, d a l a o dalao 还不承认

猜你喜欢

转载自blog.csdn.net/sugar_free_mint/article/details/86660512
今日推荐