CF Round 620 Div2 EF题解
E 1-Trees and Queries
给定一棵树,每次独立询问要求x,y之间连一条边(本来不相连),从a走到b路径长度能否恰好等于k(所有点可以无限次重复经过)
思路
可以发现,如果两点间最短路径长度≤k时,路径长度能否恰好为k只和(k-路径长度)的奇偶性相关。
所以,对于每次询问,判断三种情况:
1.a-b
2.a-x-y-b
3.a-y-x-b
只要其中有一种满足(k-其最短路径长度)为偶数,既满足,反之则不满足。
至于求两点间最短路径,只需差分思想+lca即可。
Code
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=100007;
int n,dep[maxn],fa[18][maxn];
int q,xx,yy,aa,bb,k;
vector <int> a[maxn];
void dfs(int u,int p) {
fa[0][u]=p;
for (int i=0; i<a[u].size(); i++) {
int v=a[u][i];
if (v==p) continue;
dep[v]=dep[u]+1;
dfs(v,u);
}
}
int lca(int x,int y) {
if (dep[x]<dep[y]) swap(x,y);
if (dep[x]>dep[y])
for (int i=17; i>=0; i--) if (dep[x]-dep[y]>=(1<<i))
x=fa[i][x];
if (x==y) return x;
for (int i=17; i>=0; i--) if (fa[i][x]!=fa[i][y]) {
x=fa[i][x];
y=fa[i][y];
}
return fa[0][x];
}
int dis(int x,int y) {
return dep[x]+dep[y]-2*dep[lca(x,y)];
}
int main(){
ios::sync_with_stdio(false);
cin>>n;
for (int i=1; i<n; i++){
int u,v;
cin>>u>>v;
a[u].push_back(v);
a[v].push_back(u);
}
dfs(1,0);
for (int i=1; i<=17; i++)
for (int j=1; j<=n; j++)
fa[i][j]=fa[i-1][fa[i-1][j]];
cin>>q;
while (q-->0) {
cin>>xx>>yy>>aa>>bb>>k;
bool now=false;
if (dis(aa,bb)<=k && (k-dis(aa,bb))%2==0) now=true;
if (dis(aa,xx)+dis(yy,bb)+1<=k && (k-(dis(aa,xx)+dis(yy,bb)+1))%2==0) now=true;
if (dis(aa,yy)+dis(xx,bb)+1<=k && (k-(dis(aa,yy)+dis(xx,bb)+1))%2==0) now=true;
if (now) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
F Animal Observation
Gildong要观察动物,共有n天,每天从1-m依次有m个点,每个点的动物数量给定。
1-n天中的奇数天在某点设立一个红相机,每天可以拍到连续的k个点的动物,延续两天,即覆盖一个2*k的矩形。
偶数天设立蓝相机,规则与红相机相同。
若第n天设立相机,则只照一天,现问如何安排相机可以拍到最多的动物。
思路
首先红蓝不用去考虑,反正就是每天新增一个相机。
然后因为这里要求目标值最大以及状态无后效性,容易想到dp
f[i][j]表示第i天在第j个位置(左起点)放置完一个相机后拍到的最多的动物数。(sum代表每一天的前缀和)
可以得到转移方程
f[i][j]=sum[i+1][j+k-1]-sum[i+1][j-1]+
max(
f[i-1][x]+sum[i][j+k-1]-sum[i][j-1] 1≤x≤j-k 或 j+k≤x≤m-k+1
f[i-1][x]+sum[i][j+k-1]-sum[i][x+k-1] j-k+1≤x≤j
f[i-1][x]+sum[i][x-1]-sum[i][j-1] j+1≤x≤m-k+1
)
第一种情况由于只和f[i-1][x]大小有关,所以可以在上一天预处理出f的前缀以及后缀最大值。
第二、三种情况在easy version中因为k≤20所以可以暴力解决。
在hard version中就需要数据结构进行维护,这里的下标条件很像滑动窗口,所以可以考虑用两个单调队列进行dp优化
最后时间复杂度O(nm)。
Notice:用deque获取front()或back()前一定要判断empty(),否则就会RE。
Code
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const int maxn=57;
const int maxm=20007;
int n,m,k,s[maxn][maxm],f[maxn][maxm];
int ml[maxm],mr[maxm],ans,tmp;
deque <int> q1,q2;
int main() {
ios::sync_with_stdio(false);
cin>>n>>m>>k;
for (int i=1; i<=n; i++)
for (int j=1; j<=m; j++) {
cin>>tmp;
s[i][j]=s[i][j-1]+tmp;
}
for (int i=1; i<=m-k+1; i++)
f[1][i]=s[1][i+k-1]-s[1][i-1]+s[2][i+k-1]-s[2][i-1];
for (int i=1; i<=m-k+1; i++) ml[i]=max(ml[i-1],f[1][i]);
for (int i=m-k+1; i>0; i--) mr[i]=max(mr[i+1],f[1][i]);
for (int i=2; i<=n; i++) {
for (int j=1; j<=m-k+1; j++) {
f[i][j]=s[i+1][j+k-1]-s[i+1][j-1];
int add_v=0;
if (j>k) add_v=max(add_v,ml[j-k]+s[i][j+k-1]-s[i][j-1]);
if (j+k<=m-k+1) add_v=max(add_v,mr[j+k]+s[i][j+k-1]-s[i][j-1]);
while (!q1.empty() &&
(f[i-1][q1.back()]-s[i][q1.back()+k-1])<=(f[i-1][j]-s[i][j+k-1])
) q1.pop_back();
q1.push_back(j);
while (!q1.empty() && q1.front()<j-k+1) q1.pop_front();
if (!q1.empty())
add_v=max(add_v,f[i-1][q1.front()]+s[i][j+k-1]-s[i][q1.front()+k-1]);
f[i][j]+=add_v;
}
for (int j=m-k+1; j>0; j--) {
if (j<m-k+1) if (!q2.empty())
f[i][j]=max(f[i][j],f[i-1][q2.front()]+
s[i][q2.front()-1]-
s[i][j-1]+s[i+1][j+k-1]-s[i+1][j-1]
);
while (!q2.empty() && (f[i-1][q2.back()]+s[i][q2.back()-1])<=
(f[i-1][j]+s[i][j-1])
) q2.pop_back();
q2.push_back(j);
while (!q2.empty() && q2.front()>j+k-2) q2.pop_front();
}
while (!q1.empty()) q1.pop_front();
while (!q2.empty()) q2.pop_front();
for (int j=1; j<=m-k+1; j++) ml[j]=max(ml[j-1],f[i][j]);
for (int j=m-k+1; j>0; j--) mr[j]=max(mr[j+1],f[i][j]);
}
for (int i=1; i<=m-k+1; i++) ans=max(ans,f[n][i]);
cout<<ans<<endl;
return 0;
}

浙公网安备 33010602011771号