怎么转对偶图
感性理解,平面图就是没有边相交,可以把每一块面都抠出来的图。它的对偶图就是把面扣出来作为点,有公共边的面之间连边构成的图。关键就是如何抠出面。
把每条无向边拆成两条有向边,每个面都在围成它的有向边的同一侧,这样每条边都用到且仅用到一次。这里我规定每个面在有向边的左侧。每个面逆时针找构成它的边,得到一条边后要找下一条边, 把这条边反向逆时针旋转撞到的第一条就是要找的边。
用vector和atan2实现。找到每条边的下一条边,这些下一条关系构成的所有环就是所有面。再在有公共边的面之间连边即可。
计算每个面的面积,面积为负的即为最外面的无穷大面。
题目
BZOJ1001: [BeiJing2006]狼抓兔子
传送门
平面图最小割等于对偶图最短路,这题不用上面的转法直接就可以搞出对偶图,非常好写了。
多年前的代码了,码风鬼畜啊,还写的spfa,啧啧。
//Achen
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=1000+299;
const int N=2*1000*1000+29;
int s,t,n,m,hl[maxn][maxn],sl[maxn][maxn],xl[maxn][maxn],tot;
int fir[N],nxt[N*4],to[N*4],val[N*4],ecnt,vis[N],dis[N],ans,mi1=1e9,mi2=1e9;
void add(int u,int v,int w) {
nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; val[ecnt]=w;
nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u; val[ecnt]=w;
}
queue<int>que;
void spfa() {
while(!que.empty()) que.pop();
memset(vis,0,sizeof(vis));
memset(dis,127,sizeof(dis));
dis[s]=0; vis[s]=1;
que.push(s);
while(!que.empty()) {
int now=que.front();
que.pop(); vis[now]=0;
for(int i=fir[now];i;i=nxt[i]) {
if(dis[to[i]]>dis[now]+val[i]) {
dis[to[i]]=dis[now]+val[i];
if(!vis[to[i]]) {
vis[to[i]]=1;
que.push(to[i]);
}
}
}
}
}
int main() {
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
while(scanf("%d%d",&n,&m)==2){
ecnt=0; memset(fir,0,sizeof(fir));
for(int i=1;i<=n;i++) for(int j=1;j<m;j++) {
scanf("%d",&hl[i][j]);
mi1=min(mi1,hl[i][j]);
}
for(int i=1;i<n;i++) for(int j=1;j<=m;j++) {
scanf("%d",&sl[i][j]);
mi2=min(mi2,sl[i][j]);
}
for(int i=1;i<n;i++) for(int j=1;j<m;j++) scanf("%d",&xl[i][j]);
s=0; t=(n-1)*(m-1)*2+1;
for(int i=1;i<n;i++) {
for(int j=1;j<m;j++) {
tot++;
if(j==1)
add(tot,s,sl[i][j]);
if(i==n-1)
add(tot,s,hl[i+1][j]);
add(tot,tot+1,xl[i][j]);
tot++;
if(i==1)
add(tot,t,hl[i][j]);
if(j==m-1)
add(tot,t,sl[i][j+1]);
if(i!=1)
add(tot,(i-2)*(m-1)*2+(j-1)*2+1,hl[i][j]);
if(j!=m-1)
add(tot,tot+1,sl[i][j+1]);
}
}
spfa();
if(n==1||m==1) ans=min(mi1,mi2);
else ans=dis[t];
printf("%d\n",ans);
}
return 0;
}
LOJ#2052. 「HNOI2016」矿区
吐槽
这题耗了我一下午+一晚上,本机debug n h+本机过后oj上TLE刷评测1h终于过了。。。
血的教训:
e[++ecnt]=edge(u,v,ecnt) = TLE
++ecnt; e[ecnt]=edge(u,v,ecnt) = AC
debug真的是超浪费时间的事情,而且到最后都不知道自己在写什么了,所以要一遍写对呀(臣妾真的做不到啊!)
有时候不知道到底怎么利用时间才是最好的,感觉水了一天和学了一天差别也不是很大,水的时候觉得愧疚别人在学习,学的时候又觉得效率低下还不如后面一排打游戏的人,可能就是因为这样才一直这么菜吧。。
口胡
转成对偶图后,以无穷域为根求这个图的一个生成树,题目要找的块在原图和生成树中都是一个联通块,原图中外围的边若在生成树中仍是生成树中的联通块的外围的边。故找到外围的边中在生成树中的部分就可以得到生成树中的联通块的外围边。对应树中上下边缘的外围边方向是相反的,根据这些外围边的左右边的面的父子关系即可判断是上边缘还是下边缘(上边缘的边左边的面是右边面的儿子,左边的面是联通块上最靠上的点,下边缘的边左边的面是右边面的父亲,左边的面是联通块上最靠下的点,或者反之(画图+感性理解))。
预先求得子树和,得到上下边缘后就可以直接算联通块的和了。
注意判树边的时候,因为有重边,要先判当前边在不在生成树中而不能直接判当前边的两端点是不是父子关系。
自带大常数的丑陋代码
//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<vector>
#include<cmath>
#define For(i,a,b) for(register int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(register int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=7e5+7;
typedef long long LL;
typedef double db;
using namespace std;
int n,m,q,np[N],istr[N<<1];
template<typename T> void read(T &x) {
char ch=getchar(); T f=1; x=0;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=-1,ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}
#define eps 1e-10
int dcmp(db x) {
return fabs(x)<eps?0:(x>0?1:-1); }
struct pt {
LL x,y;
pt(){
}
pt(LL x,LL y):x(x),y(y){
}
}p[N];
pt operator -(const pt&A,const pt&B) {
return pt(A.x-B.x,A.y-B.y); }
LL cross(pt A,pt B) {
return A.x*B.y-A.y*B.x; }
int ecnt=1,tid[N<<1];
struct edge {
int u,v,id; db slop;
edge(){
}
edge(int u,int v,int id,db slop):u(u),v(v),id(id),slop(slop){
}
friend bool operator <(const edge&A,const edge&B)