NOIP2018退役题解

D1T1

一眼题不说了

D1T2

其实这道题跟去年的毒瘤D1T1毫无关系,看到样例猜一个结论,剩下来的系统还是原来的那些数。那么思考什么时候几个数可以替代另一个数,那不是废话了吗。FUCK

D1T3

容易想到二分。根据菊花图我们可以知道一个配对的思路,就是一个点上来的路径一定是一一配对的,思考怎么配对是最优的,如果我二分了答案,那么我们只要再二分一次找到符合条件的最小的另外一条路即可,但是我们是在一颗树上操作,那么我们势必要传上去什么,而且一个点只能传上去一条路,那么肯定是传最大的一条不能配对的边上去,如果底下能配对就配对掉一定是最优的,因为否则传上去也最多只能造成1的贡献,和直接配对无异 FUCK

D2T1

考虑到数据范围应该是 O ( n m ) O(nm) O(nm)或者 O ( n 2 ) O(n^2) O(n2)的,我们发现树的情况非常的好搞,然鹅m=n的时候并不是一棵树,因为多了一条边,但是我们删掉就是了啊。所以枚举删边即可 FUCK

D2T2

爆搜剪枝可过
正解玄学斜链DP
考场打表系列

D2T3

一道其实不怎么难想的题,只要冷静,FUCK
首先我们还是跟D1T3一样先想部分分,如果只有一组询问,我们可以在一开始给非法状态赋上INF然后直接DP, f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示这颗子树的根的状态。如果多组询问我们还是用这个DP,考虑一下这个路径,我们可以先把lca的这颗子树拎出来考虑,那么我们可以把这颗子树的形态想象成一个从 u u u l c a lca lca v v v的链下面挂着一棵一棵的树。那么如果我们知道每个点状态下的信息我们就可以用倍增得到答案。

这个点的信息其实很好想,想一想画画图就可以知道了,这个点在路径链上的信息其实就是这个点的子树减去底下那个点的子树(或者说是链上上一个点),因为这个信息跟点对有关系所以肯定是保存在路径上的,于是就可以倍增了。初始化相邻两点的时候我们只要还原一下就好了,把本来的DP式倒过来写,扣掉那项即可,然后倍增的时候只要枚举中间节点的状态即可。最后我们还要把(整棵树-lca子树)的答案算上去,要么我们再倍增一次,从lca倍增到1,要么我们一开始再做一个树形DP,表示的是整棵树减去这个点为根的子树的代价。复杂度 O ( 2 n + n l o g n + q l o g n ) O(2n+nlogn+qlogn) O(2n+nlogn+qlogn) FUCK

D2T3代码
#include <bits/stdc++.h>
#define maxn 100005
#define LL long long
#define INF 100000000000
using namespace std;
int read(){
	int res=0,f=1; char c;
	while(!isdigit(c=getchar())) if(c=='-') f=-1;
	res=c^48;
	while(isdigit(c=getchar())) res=(res<<1)+(res<<3)+(c^48);
	return res*f;
}
struct EDGE{
	int u,v,nxt;
}e[maxn<<1];
int cnt,head[maxn];
void add(int u,int v){
	e[++cnt]=(EDGE){u,v,head[u]};
	head[u]=cnt;
}
int fa[maxn][21],dep[maxn],w[maxn];
LL f[maxn][2],g[maxn][2],d[maxn][21][2][2];
set<pair<int,int> > M;
int n,m;
void DFS(int u){
	dep[u]=dep[fa[u][0]]+1;
	f[u][1]=w[u];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v; if(v==fa[u][0]) continue; 
		fa[v][0]=u;
		DFS(v);
		f[u][0]+=f[v][1];
		f[u][1]+=min(f[v][0],f[v][1]);
	}
}
void DFS_2(int u){
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].v; if(v==fa[u][0]) continue;
		g[v][0]=g[u][1]+f[u][1]-min(f[v][0],f[v][1]);
		g[v][1]=min(g[v][0],g[u][0]+f[u][0]-f[v][1]);
		DFS_2(v);
	}
}
int main(){
	n=read(); m=read(); char s[20]; scanf("%s",s);
	for(int i=1;i<=n;i++) w[i]=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		add(u,v); add(v,u);
		M.insert(make_pair(u,v));
		M.insert(make_pair(v,u));
	}
	DFS(1); DFS_2(1);
	for(int i=2;i<=n;i++){
		d[i][0][0][0]=INF;
		d[i][0][1][0]=f[fa[i][0]][0]-f[i][1];
		d[i][0][0][1]=f[fa[i][0]][1]-min(f[i][1],f[i][0]);
		d[i][0][1][1]=f[fa[i][0]][1]-min(f[i][1],f[i][0]);
	}
	for(int j=1;j<=20;j++){
		for(int i=1;i<=n;i++){
			fa[i][j]=fa[fa[i][j-1]][j-1];
			int tmp=fa[i][j-1];
			for(int k=0;k<2;k++){
				for(int l=0;l<2;l++){
					d[i][j][k][l]=min(d[i][j-1][k][1]+d[tmp][j-1][1][l],d[i][j-1][k][0]+d[tmp][j-1][0][l]);
				}
			}
		}
	}
	for(int i=1;i<=m;i++){
		int x,y,p,q;
		LL ans[2][2],t[2];
		x=read(); p=read(); y=read(); q=read();
		if(!p && !q && M.find(make_pair(x,y))!=M.end()) {puts("-1"); continue;}
		if(dep[x]<dep[y]) swap(x,y),swap(p,q);
		ans[0][p]=f[x][p];
		ans[0][p^1]=INF;
		ans[1][q]=f[y][q];
		ans[1][q^1]=INF;
		for(int i=20;~i;i--){
			if(dep[fa[x][i]]>=dep[y]){
				t[0]=INF; t[1]=INF;
				for(int j=0;j<2;j++) for(int k=0;k<2;k++) t[j]=min(t[j],ans[0][k]+d[x][i][k][j]);
				ans[0][0]=t[0]; ans[0][1]=t[1];
				x=fa[x][i];
			}
		}
		if(x==y) {printf("%lld\n",ans[0][q]+g[y][q]); continue;}
		for(int i=20;~i;i--){
			if(fa[x][i]!=fa[y][i]){
				t[0]=INF; t[1]=INF;
				for(int j=0;j<2;j++) for(int k=0;k<2;k++) t[j]=min(t[j],ans[0][k]+d[x][i][k][j]);
				ans[0][0]=t[0]; ans[0][1]=t[1];
				t[0]=INF; t[1]=INF;
				for(int j=0;j<2;j++) for(int k=0;k<2;k++) t[j]=min(t[j],ans[1][k]+d[y][i][k][j]);
				ans[1][0]=t[0]; ans[1][1]=t[1];
				x=fa[x][i]; y=fa[y][i];
			}
		}
		int lca=fa[x][0];
		printf("%lld\n",min(f[lca][0]-f[x][1]-f[y][1]+ans[0][1]+ans[1][1]+g[lca][0],f[lca][1]-min(f[x][1],f[x][0])-min(f[y][0],f[y][1])+min(ans[0][0],ans[0][1])+min(ans[1][0],ans[1][1])+g[lca][1]));
	}
}

想退役,想读文

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jarden_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值