拆边的故事
感觉“拆边”这个说法实在不专业啊,但也只能这样了,意思到了就行了。
概述
在图论中,有的时候暴力建边的话,可能会得到一个近似于完全图的图,边数会达到 \(N^2\) 级别,这显然是不被允许的。往往这些题目,要么就不是用图论解决的,要么就是有隐藏结论可以删边(比如某玄学题目解法里就用到了奇怪的删边技巧,但盲猜这种奇技淫巧不会在大赛里出现),再就是运用一些拆边技巧了。
拆边的原理就是把原来的一条边拆成几条边,这样看似边数变多了,但往往原图里的那些边拆完之后有公共部分,拆出来的边不会特别多,以达到降低复杂度的目的。
目前做到的题目,用到拆边的,一般都是图内节点两两可达,而且边权还是用一些奇怪的公式加上两个点自身具有的一些参数计算出来的,这些情况下就可以拆边。
T1
首先说明题非原创,搬运而来,背景嘛倒是我自己写的。
题目重点就是 \(dis=\min(|x_i-x_j|,|y_i-y_j|)\) ,发现它符合上面的第二个性质“边权还是用一些奇怪的公式加上两个点自身具有的一些参数计算出来的”(自己体会什么意思吧),然后题目描述也符合“两两可达”的性质,那么可以想到拆边。
解法就是先按x坐标排序,给相邻两个点连一条无向边,边权 \(x_i-x_{i-1}\) ;再按y坐标排序,然后连边。至于取min,跑最短路的时候自然会取。这样,我们就把 \(N^2\)条 的边数优化到了 \(N\) 条。
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int N=200010;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
wh*=f;return;
}
inline int min(int s1,int s2){
return s1<s2?s1:s2;
}
inline int abs(int s1){
return s1<0?-s1:s1;
}
int m;
struct point{
int num,x,y;
}p[N];
struct edge{
int t,next;
ll v;
}e[N<<3];
int esum,head[N];
inline void add(int fr,int to,int val){
esum++;
e[esum].t=to;
e[esum].v=val;
e[esum].next=head[fr];
head[fr]=esum;
}
inline bool cmp1(point s1,point s2){
return s1.x<s2.x;
}
inline bool cmp2(point s1,point s2){
return s1.y<s2.y;
}
ll dis[N];
bool vis[N];
struct node{
int num;
ll dis;
};
inline bool operator <(node s1,node s2){
return s2.dis<s1.dis;
}
priority_queue<node>q;
signed main(){
read(m);
for(int i=1;i<=m;i++){
p[i].num=i;
read(p[i].x);read(p[i].y);
}
sort(p+1,p+m+1,cmp1);
for(int i=1;i<m;i++){
add(p[i].num,p[i+1].num,p[i+1].x-p[i].x);
add(p[i+1].num,p[i].num,p[i+1].x-p[i].x);
}
sort(p+1,p+m+1,cmp2);
for(int i=1;i<m;i++){
add(p[i].num,p[i+1].num,p[i+1].y-p[i].y);
add(p[i+1].num,p[i].num,p[i+1].y-p[i].y);
}
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
q.push((node){1,0});
while(!q.empty()){
node now=q.top();q.pop();
int wh=now.num;ll nd=now.dis;
if(vis[wh])continue;vis[wh]=true;
for(int i=head[wh],th;i;i=e[i].next){
th=e[i].t;
if(dis[wh]+e[i].v<dis[th]){
dis[th]=dis[wh]+e[i].v;
q.push((node){th,dis[th]});
}
}
}
printf("%lld",dis[m]);
return 0;
}
T2
同样,理论上来说这个图里两两可达,而距离 \(|i-j|\) 也满足条件,然后就可以拆边,方法应该不难想。至于题目中的限制条件,那是分层图干的事。
#include<cstdio>
#include<cstring>
#include<queue>
#define id(s1,s2) (s1*m+s2)
//#define zczc
using namespace std;
const int N=50010;
const int M=52;
const int S=N*M;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
wh*=f;return;
}
inline bool get(){
char w=getchar();
while(w!='0'&&w!='1')w=getchar();
return w=='1';
}
int m,n,a[N],b[M][M];
struct edge{
int t,v,next;
}e[S<<2];
int esum,head[S];
inline void add(int fr,int to,int val){
esum++;
e[esum].t=to;
e[esum].v=val;
e[esum].next=head[fr];
head[fr]=esum;
}
int dis[S];
bool vis[S];
struct node{
int wh,dis;
};
bool operator <(node s1,node s2){
return s2.dis<s1.dis;
}
priority_queue<node>q;
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
read(m);read(n);
for(int i=1;i<=m;i++)read(a[i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
b[i][j]=get();
}
}
for(int i=1;i<=m;i++)add(id(0,i),id(a[i],i),0);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(b[i][a[j]]){
add(id(i,j),id(0,j),0);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<m;j++){
add(id(i,j),id(i,j+1),1);
add(id(i,j+1),id(i,j),1);
}
}
memset(dis,0x3f,sizeof(dis));
dis[id(0,1)]=0;q.push((node){id(0,1),0});
while(!q.empty()){
int wh=q.top().wh,nd=q.top().dis;q.pop();
if(vis[wh])continue;vis[wh]=true;
for(int i=head[wh],th;i;i=e[i].next){
th=e[i].t;
if(dis[wh]+e[i].v<dis[th]){
dis[th]=dis[wh]+e[i].v;
q.push((node){th,dis[th]});
}
}
}
printf("%d",dis[id(0,m)]>1e8?-1:dis[id(0,m)]);
return 0;
}
一如既往,万事胜意

浙公网安备 33010602011771号