10.22 NOTE
P9352 [JOI 2023 Final] 训猫 / Cat Exercise
思路
要求猫移动次数的最大值,显然,当只留了一条路时, 猫的移动方向是固定的,也就是说,我们可以决定这只猫走的方向,而这是一个树形结构,显然可以树形 DP。
但是要注意:
题目有一个条件是,只能走向次高点,也就是说,我们必须根据权值从小到大来 DP,那么通常的建图方法是不适用的,正确性直接没了,但是观察到结点的权值是 1~n 中的所有数,且不重复,所以可以考虑根据每个结点的权值建边,通过并查集维护已经处理过的连通块的权值最大点,通过枚举 \(1\) 到 \(n\) 实现转移,一定是正确的。
总结
并查集
当题目中有图上动态连通性的问题、需要维护一个集合的代表等问题时,考虑使用并查集。
关于转换题目
当熟悉的建图方法在一些题中会丢失正确性且根据其他方式建图时能够保证正确性时,可以考虑一下。
一定要注意查看数据范围
Code
#include<bits/stdc++.h>
#define Iseri namespace
#define Nina std
#define Kawaragi int
#define Momoka main
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define ll long long
#define ull unsigned long long
#define endl "\n"
#define pii pair<ll,ll>
const int maxn=1000005;
const int inf=0x3f3f3f3f;
using Iseri Nina;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
//===========================================================
ll n,p[maxn],x,y,f[maxn],fa[maxn][20],lg[maxn],dep[maxn],dp[maxn];
vector<ll>v[maxn];
inline ll find(ll x){
if(x==f[x])return x;
else return f[x]=find(f[x]);
}
inline void dfs(ll u,ll lst){
fa[u][0]=lst;
dep[u]=dep[lst]+1;
for(ll i=1;(1<<i)<=dep[u];i++)fa[u][i]=fa[fa[u][i-1]][i-1];
for(auto i:v[u]){
if(i==lst)continue;
dfs(i,u);
}
return;
}
inline ll lca(ll a,ll b){
if(dep[a]<dep[b])swap(a,b);
while(dep[a]>dep[b])a=fa[a][lg[dep[a]-dep[b]]-1];
if(a==b)return a;
for(ll i=lg[dep[a]];i>=0;i--)if(fa[a][i]!=fa[b][i])a=fa[a][i],b=fa[b][i];
return fa[a][0];
}
inline ll dis(ll a,ll b){return dep[a]+dep[b]-(2*dep[lca(a,b)]);}
Kawaragi Momoka(){
n=read();
for(ll i=1;i<=n;i++){
p[i]=read();
f[i]=i;
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
for(ll i=1;i<n;i++){
x=read(),y=read();
v[p[x]].push_back(p[y]);
v[p[y]].push_back(p[x]);
}
dfs(1,0);
for(ll u=1;u<=n;u++){
for(auto i:v[u]){
ll t=find(i);
if(t<u)f[t]=u,dp[u]=max(dp[u],dp[t]+dis(u,t));
}
}
printf("%lld",dp[n]);
return 0;
}
P2403 [SDOI2010] 所驼门王的宝藏
思路
挺简单的。通过观察就可以知道这是一个在 DAG 上 DP 的题目,然而其中可能有环,所以我们可以先 Tarjan 缩点缩成一个 DAG 之后再 DP 即可。
难点主要在如何建图。由于结点数太多,所以我们可以建一个特殊点代表每一行/每一列的点,建图时朝着这个特殊点连边即可,特殊点的点权设为0就不会影响正确性了。
这个很难想,需要细思,但是想到后会觉得很妙。
总结
建图方法
对于结点数很多,而且点之间有特殊约束(比如在网格图上在同一行/同一列)时,考虑建特殊点建图。
Code
#include<bits/stdc++.h>
#define Iseri namespace
#define Nina std
#define Kawaragi int
#define Momoka main
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define ll long long
#define ull unsigned long long
#define endl "\n"
#define pii pair<ll,ll>
const int maxn=2100005;
const int inf=0x3f3f3f3f;
using Iseri Nina;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
//===========================================================
struct node{
int x,y,z;
}a[maxn];
int n,r,c,def[maxn],w[maxn],f[maxn],dfn[maxn],low[maxn],inst[maxn],t,cnt,rd[maxn];
vector<int>e[maxn],v[maxn];
stack<int>s;
int dx[8]={1,1,0,-1,-1,-1,0,1};
int dy[8]={0,-1,-1,-1,0,1,1,1};
inline bool cmp(node a,node b){return (a.x!=b.x)?a.x<b.x:a.y<b.y;}
inline int get(node u){
int l=1,r=n;
while(l<=r){
int mid=(l+r)>>1;
if(a[mid].x==u.x&&a[mid].y==u.y)return mid;
else if(a[mid].x<u.x||a[mid].x==u.x&&a[mid].y<u.y)l=mid+1;
else r=mid-1;
}
return -1;
}
inline void tarjan(int u){
dfn[u]=low[u]=++t;
s.push(u);inst[u]=1;
for(auto i:e[u]){
if(dfn[i]==0){
tarjan(i);
low[u]=min(low[u],low[i]);
}else{
if(inst[i])low[u]=min(low[u],dfn[i]);
}
}
if(dfn[u]==low[u]){
cnt++;
while(s.top()!=u){
int tmp=s.top();s.pop();
inst[tmp]=0;
def[tmp]=cnt;
w[cnt]+=(tmp>r+c)?1:0;
}
def[u]=cnt;
w[cnt]+=(u>r+c)?1:0;
s.pop();inst[u]=0;
}
return;
}
Kawaragi Momoka(){
n=read(),r=read(),c=read();
for(int i=1;i<=n;i++)a[i].x=read(),a[i].y=read(),a[i].z=read();
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++){
e[a[i].x].push_back(r+c+i);
e[a[i].y+r].push_back(r+c+i);
if(a[i].z==1)e[r+c+i].push_back(a[i].x);
else if(a[i].z==2)e[r+c+i].push_back(a[i].y+r);
else{
for(int j=0;j<=7;j++){
node tmp;
tmp.x=a[i].x+dx[j];
tmp.y=a[i].y+dy[j];
if(tmp.x>=1&&tmp.x<=r&&tmp.y>=1&&tmp.y<=c){
int res=get(tmp);
if(res!=-1)e[r+c+i].push_back(r+c+res);
}
}
}
}
int top=r+c+n;
for(int i=1;i<=top;i++)if(!dfn[i])tarjan(i);
for(int i=1;i<=top;i++){
for(auto j:e[i]){
if(def[j]!=def[i])v[def[i]].push_back(def[j]),rd[def[j]]++;
}
}
queue<int>q;
for(int i=1;i<=cnt;i++)if(rd[i]==0)q.push(i),f[i]=w[i];
while(!q.empty()){
int u=q.front();q.pop();
for(auto i:v[u]){
f[i]=max(f[i],f[u]+w[i]);
if(--rd[i]==0)q.push(i);
}
}
int ans=0;
for(int i=1;i<=cnt;i++)ans=max(ans,f[i]);
printf("%d",ans);
return 0;
}
十年 OI 一场空,开了 \(long\) \(long\) 见祖宗!!!

浙公网安备 33010602011771号