P5024 [NOIP2018 提高组] 保卫王国
思路:
首先想到每次询问两个点后就从这两个点开始往上爬,沿路更新 dp 值即可。
#include <bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Rev(i,a,b) for(int i=a;i>=b;i--)
#define clr(a,v) memset(a,v,sizeof(a))
#define Freopen(file) \
freopen(file".in","r",stdin); \
freopen(file".out","w",stdout);
#define int long long
using namespace std; const int N=2e5+5; string CharlieVinnie;
int n,Q,a[N];
vector<int> to[N];
int fa[N],dep[N];
int f[N][2],g[N][2];
set<int> got[N]; void dfs(int u,int pa)
{
fa[u]=pa;
dep[u]=dep[pa]+1; f[u][0]=0;
f[u][1]=a[u]; int sz=to[u].size();
For(i,0,sz-1){
int v=to[u][i];
if(v==pa) continue;
dfs(v,u); f[u][0]+=f[v][1];
f[u][1]+=min(f[v][0],f[v][1]);
}
} int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]) x=fa[x];
while(x!=y){
x=fa[x];
y=fa[y];
}
return x;
} int read()
{
char ch;
while((ch=getchar())<'0'||ch>'9') ;
int x=ch-'0';
while((ch=getchar())>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0';
return x;
} signed main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0); // cin>>n>>Q>>CharlieVinnie;
n=read();
Q=read();
cin>>CharlieVinnie; // For(i,1,n) cin>>a[i];
For(i,1,n) a[i]=read(); For(i,1,n-1){
int x,y;
x=read();
y=read();
// cin>>x>>y;
to[x].push_back(y);
to[y].push_back(x);
got[x].insert(y);
got[y].insert(x);
} dfs(1,0); while(Q--){
int x,tx,y,ty;
// cin>>x>>tx>>y>>ty;
x=read();
tx=read();
y=read();
ty=read(); if(tx==0&&ty==0&&got[x].count(y)){
puts("-1");
continue;
} if(x==y&&tx!=ty){
puts("-1");
continue;
} if(dep[x]<dep[y]){
swap(x,y);
swap(tx,ty);
} int z=lca(x,y); if(x!=z&&y!=z){
g[x][tx]=f[x][tx];
g[x][tx^1]=1e14; g[y][ty]=f[y][ty];
g[y][ty^1]=1e14; while(fa[x]!=z){
int fx=fa[x];
g[fx][0]=f[fx][0]-f[x][1]+g[x][1];
g[fx][1]=f[fx][1]-min(f[x][0],f[x][1])+min(g[x][0],g[x][1]);
x=fx;
} while(fa[y]!=z){
int fy=fa[y];
g[fy][0]=f[fy][0]-f[y][1]+g[y][1];
g[fy][1]=f[fy][1]-min(f[y][0],f[y][1])+min(g[y][0],g[y][1]);
y=fy;
} g[z][0]=f[z][0]-f[x][1]+g[x][1]-f[y][1]+g[y][1];
g[z][1]=f[z][1]-min(f[x][0],f[x][1])+min(g[x][0],g[x][1])-min(f[y][0],f[y][1])+min(g[y][0],g[y][1]);
}
else{
g[x][tx]=f[x][tx];
g[x][tx^1]=1e14; while(x!=z){
int fx=fa[x];
g[fx][0]=f[fx][0]-f[x][1]+g[x][1];
g[fx][1]=f[fx][1]-min(f[x][0],f[x][1])+min(g[x][0],g[x][1]);
x=fx;
} g[z][ty^1]=1e14;
} while(z!=1){
int fz=fa[z];
g[fz][0]=f[fz][0]-f[z][1]+g[z][1];
g[fz][1]=f[fz][1]-min(f[z][0],f[z][1])+min(g[z][0],g[z][1]);
z=fz;
} cout<<g[1][0]<<' '<<g[1][1]<<endl; int ans=min(g[1][0],g[1][1]); // if(ans!=1e14) cout<<ans<<endl;
// else cout<<-1<<endl;
printf("%lld\n",ans);
// if(ans!=1e14)
// else puts("-1");
} return 0;
}
然后就是怎么优化这一过程。
原来是想着直接暴力推公式找规律,然后就开始写。
发现如果令 $f[x][1] \geq f[x][0]$ 恒成立的话转移方程很漂亮,于是就“如果 $f[x][1] < f[x][0]$,也就是放人比不放还赚,于是不妨设放人,即 $f[x][0]=\min(f[x][0],f[x][1])$”
写完一测样例就错了。
原因是国王会要求在某个点不放人,所以“放人比不放还赚”不成立。
正确的思路是:
假设一开始 dp 出来的结果为 $f[x][0/1]$,询问过程中的新 dp 结果为 $g[x][0/1]$。
可以发现 $g[x][0/1]$ 到 $g[fx][0/1]$ 的转移可以表示为 $g[fx][0/1]=min(g[x][0]+c0,g[x][1]+c1)$,其中 $c0,c1$ 为一个表达式。
于是就用广义矩阵乘法优化,树上倍增的事情也解决了。
最终代码:
#include <bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Rev(i,a,b) for(int i=a;i>=b;i--)
#define clr(a,v) memset(a,v,sizeof(a))
#define Freopen(file) \
freopen(file".in","r",stdin); \
freopen(file".out","w",stdout);
#define int long long
using namespace std; const int N=2e5+5; // let df[x]=f[x][1]-f[x][0]
//
// d[x][0]=g[x][0]-f[x][0]
// d[x][1]=g[x][1]-f[x][1]
//
// d[fx][0]=d[x][1]=min(d[x][0]+inf,d[x][1]+0)
// d[fx][1]=min(f[x][0]+d[x][0],f[x][1]+d[x][1])-min(f[x][0],f[x][1])
// =min(d[x][0],(f[x][1]-f[x][0])+d[x][1])-min(0,f[x][1]-f[x][0])
// =min(d[x][0]-min(0,df[x]),d[x][1]+df[x]-min(0,df[x]))
//
// [ inf , -min(0,df[x]) ]
// [ 0 , df[x]-min(0,df[x]) ] struct Matrix{
int a[2][2]; Matrix()
{
clr(a,0x3f);
} friend Matrix operator* (const Matrix& x,const Matrix& y)
{
Matrix z;
For(i,0,1){
For(j,0,1){
For(k,0,1){
z.a[i][j]=min(z.a[i][j],x.a[i][k]+y.a[k][j]);
}
}
}
return z;
}
}; string CharlieVinnie;
int n,Q,a[N];
vector<int> to[N];
int fa[N][21],dep[N];
int f[N][2],df[N];
Matrix mat[N][21]; int read()
{
char ch;
while((ch=getchar())<'0'||ch>'9') ;
int x=ch-'0';
while((ch=getchar())>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0';
return x;
} void dfs(int u,int pa)
{
fa[u][0]=pa;
dep[u]=dep[pa]+1; f[u][0]=0;
f[u][1]=a[u]; int sz=to[u].size();
For(i,0,sz-1){
int v=to[u][i];
if(v==pa) continue;
dfs(v,u); f[u][0]+=f[v][1];
f[u][1]+=min(f[v][0],f[v][1]);
} df[u]=f[u][1]-f[u][0];
} int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y); Rev(i,20,0){
if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
} if(x==y) return x; Rev(i,20,0){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
} return fa[x][0];
} void moveup(int& x,int z,int& dx0,int& dx1)
{
Matrix res;
res.a[0][0]=res.a[1][1]=0; Rev(k,20,0){
if(dep[fa[x][k]]>dep[z]){
res=res*mat[x][k];
x=fa[x][k];
}
} int t0=dx0,t1=dx1;
dx0=min(t0+res.a[0][0],t1+res.a[1][0]);
dx1=min(t0+res.a[0][1],t1+res.a[1][1]);
} signed main()
{
n=read();
Q=read();
cin>>CharlieVinnie; For(i,1,n) a[i]=read(); For(i,1,n-1){
int x,y;
x=read();
y=read();
to[x].push_back(y);
to[y].push_back(x);
} dfs(1,0); For(i,1,n){
mat[i][0].a[0][0]=1e14;
mat[i][0].a[0][1]=-min(0ll,df[i]);
mat[i][0].a[1][0]=0;
mat[i][0].a[1][1]=df[i]-min(0ll,df[i]);
} For(j,1,20){
For(i,1,n){
fa[i][j]=fa[fa[i][j-1]][j-1];
mat[i][j]=mat[i][j-1]*mat[fa[i][j-1]][j-1];
}
} while(Q--){
int x,tx,y,ty;
x=read();
tx=read();
y=read();
ty=read(); if(dep[x]<dep[y]){
swap(x,y);
swap(tx,ty);
} if(tx==0&&ty==0&&fa[x][0]==y){
puts("-1");
continue;
} if(x==y&&tx!=ty){
puts("-1");
continue;
} int z=lca(x,y); int dz0=0,dz1=0; if(y!=z){
int dx0=0,dx1=0;
if(tx==0) dx1=1e14;
else dx0=1e14;
moveup(x,z,dx0,dx1); int dy0=0,dy1=0;
if(ty==0) dy1=1e14;
else dy0=1e14;
moveup(y,z,dy0,dy1); dz0=dx1+dy1;
dz1=min(dx0-min(0ll,df[x]),dx1+df[x]-min(0ll,df[x]))+min(dy0-min(0ll,df[y]),dy1+df[y]-min(0ll,df[y]));
}
else{
int dx0=0,dx1=0;
if(tx==0) dx1=1e14;
else dx0=1e14; moveup(x,fa[z][0],dx0,dx1); dz0=dx0;
dz1=dx1; if(ty==0) dz1=1e14;
else dz0=1e14;
} moveup(z,0,dz0,dz1); printf("%lld\n",min(f[1][0]+dz0,f[1][1]+dz1));
} return 0;
}
P5024 [NOIP2018 提高组] 保卫王国的更多相关文章
- [NOIP2018提高组] 保卫王国 (树链剖分+动态DP)
题面 题目链接-Luogu 题目链接-Loj(要加Freopen) 题解 什么是动态DP? OneInDark:你不需要知道这么多,你只需要知道是利用了广义矩阵乘法就够了! 广义矩乘 广义矩阵乘法,简 ...
- [NOIp2018提高组]旅行
[NOIp2018提高组]旅行: 题目大意: 一个\(n(n\le5000)\)个点,\(m(m\le n)\)条边的连通图.可以从任意一个点出发,前往任意一个相邻的未访问的结点,或沿着第一次来这个点 ...
- [NOIp2018提高组]赛道修建
[NOIp2018提高组]赛道修建 题目大意: 给你一棵\(n(n\le5\times10^4)\)个结点的树,从中找出\(m\)个没有公共边的路径,使得第\(m\)长的路径最长.问第\(m\)长的路 ...
- [NOIp2018提高组]货币系统
[NOIp2018提高组]货币系统 题目大意: 有\(n(n\le100)\)种不同的货币,每种货币的面额为\([1,25000]\)之间的一个整数.若两种货币系统能够组合出来的数是相同的的,那我们就 ...
- [NOIp2013提高组]积木大赛/[NOIp2018提高组]铺设道路
[NOIp2013提高组]积木大赛/[NOIp2018提高组]铺设道路 题目大意: 对于长度为\(n(n\le10^5)\)的非负数列\(A\),每次可以选取一个区间\(-1\).问将数列清零至少需要 ...
- NOIP2018提高组省一冲奖班模测训练(六)
NOIP2018提高组省一冲奖班模测训练(六) https://www.51nod.com/Contest/ContestDescription.html#!#contestId=80 20分钟AC掉 ...
- NOIP2018提高组省一冲奖班模测训练(五)
NOIP2018提高组省一冲奖班模测训练(五) http://www.51nod.com/Contest/ContestDescription.html#!#contestId=79 今天有点浪…… ...
- NOIP2018提高组金牌训练营——动态规划专题
NOIP2018提高组金牌训练营——动态规划专题 https://www.51nod.com/Live/LiveDescription.html#!#liveId=19 多重背包 二进制优化转化成01 ...
- NOIP2018提高组省一冲奖班模测训练(四)
NOIP2018提高组省一冲奖班模测训练(四) 这次比赛只AC了第一题,而且花了40多分钟,貌似是A掉第一题里面最晚的 而且还有一个半小时我就放弃了…… 下次即使想不出也要坚持到最后 第二题没思路 第 ...
随机推荐
- 联盛德 HLK-W806 (九): 软件SPI和硬件SPI驱动ST7789V液晶LCD
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- CF615A Bulbs 题解
Content 有 \(n\) 个灯,一开始它们都是关着的.有 \(m\) 个按钮,每个按钮可以开 \(k\) 盏灯.求能否通过这 \(m\) 个按钮使得所有灯全部都开着. 数据范围:\(1\leqs ...
- CF1139A Even Substrings 题解
Content 有一个长度为 \(n\) 的数字串 \(s\),试求出代表偶数的子串个数. 数据范围:\(1\leqslant n\leqslant 65000\),\(s\) 仅包含数字 \(1\s ...
- 『学了就忘』Linux系统定时任务 — 89、任务调度工具anacron
目录 1.任务调度工具anacron介绍 2.新旧版本Linux中anacron工具的区别 3./etc/cron.{daily,weekly,monthly}目录说明 4.anacron命令 5./ ...
- 【科普】.NET6 泛型
本文内容来自我写的开源电子书<WoW C#>,现在正在编写中,可以去WOW-Csharp/学习路径总结.md at master · sogeisetsu/WOW-Csharp (gith ...
- Java 中接口和抽象类的 7 大区别!
本文已收录<Java常见面试题>:https://gitee.com/mydb/interview Java 是一门面向对象的编程语言,面向对象的编程语言有四大特征:抽象.封装.继承和 ...
- java 多线程 发布订阅模式:发布者java.util.concurrent.SubmissionPublisher;订阅者java.util.concurrent.Flow.Subscriber
1,什么是发布订阅模式? 在软件架构中,发布订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者).而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话 ...
- 基于MirrorDriver的录屏技术
计算机屏幕图像的截取在屏幕的录制.计算机远程控制以及多媒体教学软件中都是关键术,基于Windows操作系统有多种截屏方法,研究的重点集中在如何快速有效的截取DBI(Device-Independent ...
- uniapp+nvue开发之仿微信语音+视频通话功能 :实现一对一语音视频在线通话
本篇文章是利用uni-app和nvue实现微信效果功能的第三篇了,今天我们基于uniapp + nvue实现的uniapp仿微信音视频通话插件实例项目,实现了以下功能: 1: 语音通话 2: 视频 ...
- windows系统中远程登录ubuntu18服务器的桌面
下载:http://www.c-nergy.be/products.html 也可以直接服务器里 wget https://www.c-nergy.be/downloads/xRDP/xrdp-ins ...