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

111 篇文章 0 订阅
76 篇文章 0 订阅

前言

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


B组部分

JZOJ 1252 洛谷 5194 天平

题目

n n n个数中选择若干个,使它们的和不超过 c 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 n号点,不能停留,在每一个周期每条单向路有一定的费用,问到第 m 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] f[j]表示空间为 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]=f[jw[i]]+c[i],c[i]w[i],然后只要 f [ j ] ≥ p f[j]\geq p f[j]p那么对 j 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) (x,y)至少有一条最大边权不超过 d d 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)) (son[x]+son[y])×(son[x]+son[y]1)(son[x]×(son[x1])+son[y]×(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] dp1[x]表示从 x x x到根节点的期望步数, d p 2 [ x ] dp2[x] dp2[x]表示从根节点到 x x x的期望步数,但是有点难,那首先从父节点开始,按照YYT的方法,然后用前缀和求得两个数组,然后最后对于两个点 x , y 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] dp1[x]dp1[lca]+dp2[y]dp2[lca]
首先 d p 1 ′ [ x ] dp1'[x] dp1[x]表示从 x x x到父节点的期望步数, d p 2 ′ [ x ] dp2'[x] dp2[x]表示父节点到 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'[x]=\frac{1}{deg[x](度数)}+(\sum_{i\in son_x}1+dp1'[i]+dp1'[x](到儿子再到自己再到父节点))\div deg[x] dp1[x]=deg[x]()1+(isonx1+dp1[i]+dp1[x]())÷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'[x]=\frac{1}{deg[x]}+\frac{deg[x]-1(子节点个数)}{deg[x]}+\sum_{i\in son_x}dp1'[i]\div deg[x]+\frac{(deg[x]-1)dp1'[x]}{deg[x]} dp1[x]=deg[x]1+deg[x]deg[x]1()+isonxdp1[i]÷deg[x]+deg[x](deg[x]1)dp1[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'[x]}{deg[x]}=1+\sum_{i\in son_x}dp1'[i]\div deg[x] deg[x]dp1[x]=1+isonxdp1[i]÷deg[x]
方程两边同乘 d e g [ x ] deg[x] deg[x]得到最后结果
d p 1 ′ [ x ] = d e g [ x ] + ∑ i ∈ s o n x d p 1 ′ [ i ] dp1'[x]=deg[x]+\sum_{i\in son_x}dp1'[i] dp1[x]=deg[x]+isonxdp1[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'[x]=\frac{1}{deg[fa]}+(\sum_{i\in x的兄弟}1+dp1'[i]+dp2'[x](到父亲再到兄弟再回去再回到x))\div {deg[fa]}+\frac{1+dp2'[fa]到父亲再回来}{deg[fa]} dp2[x]=deg[fa]1+(ix1+dp1[i]+dp2[x](x))÷deg[fa]+deg[fa]1+dp2[fa]
化简方法差不多,那么就可以得到
d p 2 ′ [ x ] = d e g [ f a ] + ∑ i ∈ x 的 兄 弟 d p 1 ′ [ i ] + d p 2 ′ [ f a ] dp2'[x]=deg[fa]+\sum_{i\in x的兄弟}dp1'[i]+dp2'[fa] dp2[x]=deg[fa]+ixdp1[i]+dp2[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'[x]=deg[fa]+dp1'[fa]-dp1'[x]+dp2'[fa] dp2[x]=deg[fa]+dp1[fa]dp1[x]+dp2[fa]
被带偏了吧,没错,我那时被带偏了
观察 d p 1 ′ [ f a ] = d e g [ f a ] + ∑ i ∈ s o n x d p 1 ′ [ i ] dp1'[fa]=deg[fa]+\sum_{i\in son_x}dp1'[i] dp1[fa]=deg[fa]+isonxdp1[i]
它多出来了一个 d e g [ f a ] deg[fa] deg[fa],那就正好了,那么
d p 2 ′ [ x ] = d p 1 ′ [ f a ] − d p 1 ′ [ x ] + d p 2 ′ [ f a ] dp2'[x]=dp1'[fa]-dp1'[x]+dp2'[fa] dp2[x]=dp1[fa]dp1[x]+dp2[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 dalao还不承认

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值