0x6B「图论」练习
POJ3463 Sightseeing
求次短路和方案数统计。需要在dij算法松弛时进行修正。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
6B02 升降梯上
从初始状态spfa跑单源最短路即可。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
6B03 GF和猫咪的玩具
所谓“拉紧”,就等价于找到两点间的最短路。因此本题所求就是所有两点间最短路中最长的。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
POJ2349 Arctic Network
按照两点间距离作为边权建边,求出MST后,输出第s大的边长即可。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
6B06 四叶草魔杖
正解链接 https://www.cnblogs.com/lokiii/p/9342507.html
但我的解法也AC了。暂不知道原因。
说一下我的解法。
由于最后如果存在可行解,会有若干的子图。因此只要跑一次MST,最后判断是否会有权值和不为0的集合即可,若存在,则无解。否则答案就是MST的边权和。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
https://www.cnblogs.com/lokiii/p/9342507.html
*/
#include
#include
#include
#include
#include
#include
POJ1275 Cashier Employment
差分约束,具体详见代码。
#include
#include
#include
#include
#include
using namespace std;
const int MAXN=1005;
const int INF=(1<<30)-1;
struct Edge {
int to,nxt,w;
Edge() {
to=nxt=w=-1; //构造函数 仅用于初始化
}
} e[MAXN];
int first[MAXN];
int t,n;
int tot;
int d[MAXN];
int in[MAXN];
int cnt[MAXN];
int R[MAXN];
int num[MAXN];
int inihead[MAXN];
queue q;
void inline Add_Edge(int x,int y,int w) { //链式前向星
tot++;
e[tot].to=y;
e[tot].w=w;
e[tot].nxt=first[x]; //e的下一个是前一个 当第一次进来时 e的上一个不存在 即fisrt的初始值是-1
first[x]=tot;
}
void init() {
int a;
for(int i=1; i<=24; i++)
cin>>R[i]; //1~24点 每个时间需要的人数
cin>>n; //应聘者人数
memset(num,0,sizeof(num));
for(int i=1; i<=n; i++) {
cin>>a;
num[a+1]++; //输入是0~23点 所以这里要+1
}
tot=0;//初始化链式前向星的下标
memset(first,-1,sizeof(first)); //这里的first数组相当于 链式前向星的head数组
memset(e,-1,sizeof(-1));
/*
s[ I ]-s[ I-1 ]>=0 (0<=I<=23)
s[ I-1 ]-s[ I ]>=-num[ I ] (0<=I<=23)
s[ I ]-s[ I-8 ]>=r[ I ] (8<=I<=23)
s[ I ]-s[ I+16 ]>=r[ I ]-s[ 23 ] (0<=I<= 7)
*/
for(int i=1; i<=24; i++) {
if(i>7) Add_Edge(i-8,i,R[i]);
//如果要求最小值的话,变为x-y>=k的标准形式,然后建立一条从y到x的k边,求出最长路径即可
Add_Edge(i,i-1,-num[i]);
Add_Edge(i-1,i,0);
}
for(int i=0; i<25; i++)
inihead[i]=first[i];
//用一个数组暂时存储 因为初始化只有一次 但是二分答案的循环中有加边的操作 这会让first数组改变
}
bool SPFA(int mid) {
for(int i=0; i<=24; i++)
d[i]=-INF,in[i]=0,cnt[i]=0;
q.push(0);
in[0]=1;
cnt[0]++;
d[0]=0;
/*
初始化
原点入队
栈数组 原点为1 其他为0
计数数组 原点为1 其他为0
到原点距离的数组 原点为0 其他为-INF
注意:这里有25个点 0点是虚拟原点 这意味着 其他都需要从1开始存储
————————————————
*/
while(!q.empty()) {
int x=q.front();
q.pop();
in[x]=0; //出栈
for(int i=first[x]; i!=-1; i=e[i].nxt) {
int y=e[i].to;
int w=e[i].w;
if(d[y]25) //进入次数超过理论上限(即结点个数) 说明存在负权回路
return false;
}
}
}
}
return true;
}
int main() {
ios::sync_with_stdio(false);
// freopen("小胖超市.in","r",stdin);
cin>>t;
while(t--) {
init();
int l=0,r=n; //二分答案
int ans=INF;
while(l=ans(s[0]=0)
if(SPFA(mid)) {
r=mid; //可以的话 缩小最大值
ans=min(ans,mid);//可以的情况下 才可以ans求min
} else
l=mid+1;
}
if(ans>n) //为了满足条件 需要雇佣的人超过了申请者的数目 无解
cout<<"No Solution"<
6B12 最优高铁环
dfs判断负环即可。注意建图方式。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
POJ1041 John's trip
题意:求字典序最小的欧拉回路。
首先通过度数,判断是否存在欧拉回路。(所有点度数均为偶数)
然后跑一便euler()即可。注意由于存在字典序,所以要先将边排序。这里使用了链式前向星存图,因为前向星从最后一条边开始扫 所以加边的顺序是降序。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
6B18 太鼓达人
答案显然是,考虑使用dfs构造方案。
具体地说,每次优先尝试放置0,再尝试放置1。同时用一个vis数组对最后k位判重即可。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
POJ1112 Team Them Up!
6B24 Place the Robots
若是直接在所有不发生冲突的坐标点之间建边,则转化为图的最大独立集问题,NP不可解。
于是我就去网上找了题解。
考虑图的性质,因为是网格平面,因此将每一行被墙隔开、且包含空地的连续区域称作“块”。显然,在一个块之中,最多只能放一个机器人。把这些块编上号,如图7.25(a)所示。需要说明的是,最后一行,即第4 行有两个空地,但这两个空地之间没有墙壁,只有草地,所以这两个空地应该属于同一“块”。同样,把竖直方向的块也编上号,如图7.25(b)所示。
把每个横向块看作二部图中顶点集合X 中的顶点,竖向块看作集合Y 中的顶点,若两个块有公共的空地(注意,每两个块最多有一个公共空地),则在它们之间连边。例如,横向块2 和竖向块1 有公共的空地,即(2, 0),于是在X 集合中的顶点2 和Y 集合中的顶点1 之间有一条边。这样,问题转化成一个二部图,如图7.25(c)所示。由于每条边表示一个空地(即一个横向块和一个竖向块的公共空地),有冲突的空地之间必有公共顶点。例如边(x1, y1)表示空地(0, 0)、边(x2, y1)表示空地(2, 0),在这两个空地上不能同时放置机器人。所以问题转化为在二部图中找没有公共顶点的最大边集,这就是最大匹配问题。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
https://blog.csdn.net/u014141559/article/details/44409255
*/
#include
#include
#include
#include
#include
#include
POJ2195 Going Home
分析题意可知,本题为二分图最小权完备匹配。使用最小费用最大流可以AC。
具体建图方式:建立虚拟超级源点和汇点。
超级源点向所有m节点建边,容量1,费用0。
所有h节点向超级汇点建边,容量1,费用0。
每个m节点向所有h节点建边,容量1,费用为两个节点的距离。
代码如下(PS:method_1为bfs求两点距离,method_2为直接根据坐标计算)
/*
*/
#define method_1
#ifdef method_1
/*
82ms AC
*/
#include
#include
#include
#include
#include
#include
POJ1422 Air Raid
DAG最小路径覆盖,答案等价于点数-拆点二分图的最大匹配。具体地说,就是将原图上的每个点x拆成两个点x和x+n,对于每条边(x,y),建边(x,y+n)即可。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<pii;
const int maxn=120*2+5;
const int INF=0x3f3f3f3f;
int T,n,m,tot,head[maxn],ans,vis[maxn],match[maxn];
struct node{
int from,to;
}edge[maxn*4];
void add(int from,int to){
edge[++tot].from=head[from],head[from]=tot,edge[tot].to=to;
}
void init(){
ans=0,tot=1;
memset(head,0,sizeof(head));
memset(match,0,sizeof(match));
}
bool dfs(int x){
for(int i=head[x];i;i=edge[i].from){
int y=edge[i].to;
if(!vis[y]){
vis[y]=1;
if(!match[y]||dfs(match[y])){
match[y]=x;
return true;
}
}
}
return false;
}
int main() {
ios::sync_with_stdio(false);
//freopen("Air Raid.in","r",stdin);
cin>>T;
while(T--){
init();
cin>>n>>m;
int a,b;
for(int i=1;i<=m;i++) cin>>a>>b,add(a,b+n);
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ans++;
}
cout<
POJ1486 Sorting Slide
显然可以将所有数字作为二分图的左部节点,所有窗口作为二分图的右部节点。本题所求就是,判断是否存在唯一的完美匹配方案。
解决方法是,首先进行一边匈牙利算法。然后依次尝试去掉每一条边,去掉后再跑一边匈牙利算法,判断匹配数是否仍旧是n,如果不是,说明这条边的对应关系是确定的。
代码如下
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<pii;
const int maxn=24*2+5;
const int INF=0x3f3f3f3f;
int kase=0,n,mp[maxn][maxn],match[maxn],vis[maxn],ans,path[maxn];
struct Slide{
int X1,X2,Y1,Y2;
}slide[maxn];
struct Point{
int x,y;
}p[maxn];
void init(){
memset(mp,0,sizeof(mp));
}
bool check(int i,int j){ //判断第i个矩形是否包含第j个点
return (p[j].x>slide[i].X1)&&(p[j].xslide[i].Y1)&&(p[j].y>n){
if(n==0) break;
cout<<"Heap "<<++kase<>slide[i].X1>>slide[i].X2>>slide[i].Y1>>slide[i].Y2;
for(int i=1;i<=n;i++) cin>>p[i].x>>p[i].y;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
if(check(i,j)){
mp[j][i]=1;
}
}
hungray();
for(int i=1;i<=n;i++){
path[i]=match[i];
}
int flag=0;
for(int i=1;i<=n;i++){
mp[path[i]][i]=0; //如果删掉某一条边后变不完美了,说明这条边的对应关系是确定的。
hungray();
if(ans==n) continue;
else{
if(flag) cout<<" "; //去除行尾空格
cout<<"("<
POJ1904 King's Quest
二分图完美匹配的可行边和必须边问题,这里有一篇题解讲的很好。
链接 https://blog.csdn.net/a709743744/article/details/52133778
代码如下
/*
https://blog.csdn.net/a709743744/article/details/52133778
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<pii;
const int maxn=2000*2+5;
const int INF=0x3f3f3f3f;
int n,tot=1,cnt=0,cnt1=0,top=0,head[maxn],inx[maxn],dfn[maxn],low[maxn],st[maxn],c[maxn];
vectorscc[maxn],ans[maxn];
struct node{
int from,to;
}edge[maxn*maxn*2];
void add(int from,int to){
edge[++tot].from=head[from],head[from]=tot,edge[tot].to=to;
}
void tarjan(int x){
low[x]=dfn[x]=++cnt1;
inx[x]=1;st[++top]=x;
for(int i=head[x];i;i=edge[i].from){
int y=edge[i].to;
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(inx[y]){
low[x]=min(low[x],dfn[y]);
}
}
if(dfn[x]==low[x]){
int y;cnt++;
do{
y=st[top];
top--;
inx[y]=0;
c[y]=cnt;
scc[cnt].push_back(y);
}while(x!=y);
}
}
int main() {
// ios::sync_with_stdio(false);
// freopen("King's Quest.in","r",stdin);
scanf("%d",&n);
int num,x;
for(int i=1;i<=n;i++){
scanf("%d",&num);
while(num--){
scanf("%d",&x);
add(i,x+n);
}
}
for(int i=1;i<=n;i++){
scanf("%d",&x);
add(x+n,i); //注意不是add(i+n,x);
}
for(int i=1;i<=2*n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++){
for(int j=head[i];j;j=edge[j].from){
int y=edge[j].to;
if(c[i]==c[y]) ans[i].push_back(y-n);
}
sort(ans[i].begin(),ans[i].end());
printf("%d ",ans[i].size());
for(int j=0;j
POJ1273 Drainage Ditches
最大流模板题,这里使用了dinic算法。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define D(x) cout<<#x<<" = "<pii;
const int maxn=200+5;
const int INF=0x3f3f3f3f;
int n,m,head[maxn],q[maxn],d[maxn],tot=1,flow,maxflow=0;
struct node{
int from,to,v;
}edge[maxn<<1];
void add(int from,int to,int v){
edge[++tot].to=to,edge[tot].v=v,edge[tot].from=head[from],head[from]=tot;
edge[++tot].to=from,edge[tot].v=0,edge[tot].from=head[to],head[to]=tot;
}
void init(){
tot=1,maxflow=0;
memset(head,0,sizeof(head));
}
bool bfs(){
memset(d,0,sizeof(d));
int l=1,r=1;
q[1]=1,d[1]=1;
while(l<=r){
int x=q[l];
for(int i=head[x];i;i=edge[i].from){
int y=edge[i].to,v=edge[i].v;
if(v&&!d[y]){
d[y]=d[x]+1;
q[++r]=y;
if(y==m) return 1;
}
}
l++;
}
return 0;
}
int dinic(int x,int flow){
if(x==m) return flow;
int rest=flow,k;
for(int i=head[x];i&&rest;i=edge[i].from){
int y=edge[i].to;
if((d[y]==d[x]+1)&&edge[i].v){
k=dinic(y,min(edge[i].v,rest));
if(!k) d[y]=0;
edge[i].v-=k,edge[i^1].v+=k;
rest-=k;
}
}
return flow-rest;
}
int main() {
ios::sync_with_stdio(false);
//freopen("Drainage Ditches.in","r",stdin);
while(cin>>n>>m){
init();
int a,b,c;
for(int i=1;i<=n;i++){
cin>>a>>b>>c;
add(a,b,c);
}
while(bfs())while(flow=dinic(1,INF))maxflow+=flow;
cout<