题解 Luogu P2499: [SDOI2012]象棋

关于这道题, 我们可以发现移动顺序不会改变答案, 具体来说, 我们有以下引理成立:

  • 对于一个移动过程中的任意一个移动, 若其到达的位置上有一个棋子, 则该方案要么不能将所有棋子移动到最终位置, 要么可以通过改变顺序使这一次移动合法

证明:

考虑到达位置上的那个棋子, 如果它没有到达最终位置, 则我们考虑将该棋子移至下一步, 如果下一步还有没有到达最终位置的棋子, 则也移动它

否则直接调换这两个棋子的移动顺序即可

好的我们去除了题目中的要求: 「移动过程中不能出现多颗棋子同时在某一格的情况」, 接下来考虑题目被我们转化成了什么样子:

给定\(k\)个起始点和它们到达\(k\)个终点的步数, 求一种移动方案, 使得这种移动方案能将所有起始点移动到终点(保证有方案), 其次使得所花费的步数最少.

于是我们把这道题目转化为了一个费用流问题, 具体的, 我们建立超级源点\(S\), 超级汇点\(T\), 接下来连接三类边

  1. \(S\rightarrow\)所有起始点, 这类边容量为1, 费用为0
  2. 每一个起始点\(\rightarrow\)每一个它能够到达的终点, 这类边容量为1, 费用为所消耗的步数
  3. 所有终点\(\rightarrow T\), 这类边容量为1, 费用为0

在新图上跑费用流即可AC

要是这道题有这么简单就好了

如果你使用了一般的EK+SPFA, 你会像我这样:

接下来我们考虑改进算法, ouuan在这篇帖子中说道:

常见费用流算法(把 Dinic 的 BFS 改成求最短路)复杂度上界是 \(O(nmf)\),其中 \(f\) 是最大流。

于是我们点进了下面的链接学习了那种方法并且AC本题

我不会那种方法>_< 于是使用了Dijkstra跑最短路, 事实上一般的Dijkstra是不能跑的, 但是我在费用流的题解中找到了这种神仙做法

具体来说, 我们定义势函数\(h(i)\)使得\(e'(u,v)=e(u,v)+h(u)-h(v)\), 最后对最短路的影响可以强行考虑掉, 且\(e'(u,v)\)恒非负, 就可Dijkstra了

接下来我们发现\(h(i)=mindis(u)\)是成立的, 但那样的话你还要跑SPFA求最短路

因此我们转而考虑\(h(i)=mindis_{round-1}(u)\), 其中\(mindis_{round-1}(u)\)指上一轮\(S \rightarrow u\)的最短路(不存在为0), 我们发现这也是成立的, 具体来说, 我们考虑每条边\(e(u,v)\).

  • 若\(e(u,v)\)在上一次增广时存在, 那么显然满足性质
  • 否则\(e(u,v)\)在最短路上, 也可证明\(e'(u,v) \geq 0\)

于是我们的势算法成立, 用Dijkstra替换SPFA即可在luogu上AC本题

#pragma GCC diagnostic error "-std=c++11"
#pragma GCC target("avx")
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#include<bits/stdc++.h>
using namespace std;
namespace mcmf {
    const int N=1002; const int M=500*502*2+1; const int inf=0x3f3f3f3f;
    int tot = 1, head[N], Next[M * 2], ver[M * 2], edge[M * 2], cost[M * 2];
    int dis[N], h[N], lst[N], pre[N], C, n;
    #define pi pair<int,int>
    inline void _add(int u, int v, int c, int w) {
        Next[++tot] = head[u], head[u] = tot, ver[tot] = v, edge[tot] = c, cost[tot] = w;
    }
    inline void add(int u, int v, int c, int w) {
        _add(u, v, c, w), _add(v, u, 0, -w);
    }
    void Dijkstra(int s) {
        priority_queue<pi, vector<pi>, greater<pi> > q;
        for(; !q.empty(); q.pop());
        fill(dis, dis + 1 + n, -1);
        dis[s] = 0, q.push(pi(0, s));
        while(!q.empty()) {
            pi now = q.top(); q.pop();
            int u = now.second;
            if(dis[u] < now.first) continue;
            for(int i = head[u]; i; i = Next[i]) {
                static int v; v = ver[i];
                if(!edge[i]) continue;
                if(dis[v] < 0 || dis[v] > dis[u] + cost[i] + h[u] - h[v]) {
                    dis[v] = dis[u] + cost[i] + h[u] - h[v];
                    pre[v] = u, lst[v] = i;
                    q.push(pi(dis[v], v));
                }
            }
        }
    }
    int solve(int s, int t) {
        memset(h,0,sizeof(h)); int d=inf;
        while(1){
            Dijkstra(s); if(dis[t] < 0) break;
            for(register int i = 1; i <= n; ++i) h[i] += (dis[i] != -1) ? dis[i] : 0; d=inf;
            for(int u = t; u != s; u = pre[u]) edge[lst[u]]<d && (d=edge[lst[u]]); C += h[t] * d;
            for(int u = t; u != s; u = pre[u]) edge[lst[u]] -= d, edge[lst[u] ^ 1] += d;
        }
        return C;
    }
}
const int _=501,__=101;
int n,m,k,a,b;
int un[__][__],dis[__][__];
struct pos{
    int x,y;
}st[_], ed[_];
queue<pos> Q;
int dx[8]={1,1,-1,-1,1,1,-1,-1}, dy[8]={1,-1,1,-1,1,-1,1,-1};
#define in(a) (a.x>=1 && a.x<=n && a.y>=0 && a.y<=m && !un[a.x][a.y])
void getdis(int s){
    Q.push(st[s]); dis[st[s].x][st[s].y]=0; pos x; int d;
    while(!Q.empty()){
        x=Q.front(); d=dis[x.x][x.y]; Q.pop();
        for(int i=0;i<8;++i){
            x.x+=dx[i]; x.y+=dy[i];
            (dis[x.x][x.y]==-1) && in(x) && (Q.push(x),dis[x.x][x.y]=d+1);
            x.x-=dx[i]; x.y-=dy[i];
        }
    }
}
char get(){char c=getchar(); while(c!='*' && c!='.') c=getchar(); return c;}
int main(){
    scanf("%d%d%d%d%d",&n,&m,&k,&a,&b);
    for(register int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
        char ch=get(); un[i][j]=(ch=='*');
    }
    for(register int i=0;i<4;++i) dx[i]*=a,dy[i]*=b;
    for(register int i=4;i<8;++i) dx[i]*=b,dy[i]*=a;
    for(register int i=1;i<=k;++i) scanf("%d%d",&st[i].x,&st[i].y);
    for(register int i=1;i<=k;++i) scanf("%d%d",&ed[i].x,&ed[i].y);
    for(register int i=1;i<=k;++i) {
        memset(dis,-1,sizeof(dis)); getdis(i);
        for(int j=1;j<=k;++j) if(dis[ed[j].x][ed[j].y]!=-1) mcmf::add(i+1,j+k+1,1,dis[ed[j].x][ed[j].y]);
    }
    for(int i=1;i<=k;++i) mcmf::add(1,i+1,1,0),mcmf::add(i+k+1,2*k+2,1,0);
    mcmf::n=2*k+2; printf("%d\n",mcmf::solve(1,2*k+2));
}

原文地址:https://www.cnblogs.com/tyqtyq/p/12024011.html

时间: 2024-08-30 03:09:32

题解 Luogu P2499: [SDOI2012]象棋的相关文章

题解 luogu P1850 【换教室】

题解 luogu P1850 [换教室] 时间:2019.8.6 一晚上(约 3.5h 写完) 题目描述 对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程. 在可以选择的课程中,有 \(2n\) 节课程安排在 \(n\) 个时间段上.在第 \(i\)(\(1 \leq i \leq n\))个时间段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教室 \(c_i\) 上课,而另一节课程在教室 \(d_i\) 进行. 在不提交任何申请的情况下,学生们需要

题解 luogu P5021 【赛道修建】

题解 luogu P5021 [赛道修建] 时间:2019.8.9 20:40 时间:2019.8.12 题目描述 C 城将要举办一系列的赛车比赛.在比赛前,需要在城内修建 \(m\) 条赛道. C 城一共有 \(n\) 个路口,这些路口编号为 \(1,2,\dots,n\),有 \(n-1\) 条适合于修建赛道的双向通行的道路,每条道路连接着两个路口.其中,第 \(i\) 条道路连接的两个路口编号为 \(a_i\) 和 \(b_i\),该道路的长度为 \(l_i\).借助这 \(n-1\) 条

【洛谷题解】P2303 [SDOi2012]Longge的问题

题目传送门:链接. 能自己推出正确的式子的感觉真的很好! 题意简述: 求\(\sum_{i=1}^{n}gcd(i,n)\).\(n\leq 2^{32}\). 题解: 我们开始化简式子: \(\sum_{i=1}^{n}gcd(i,n)\) \(=\sum_{j=1}^{n}\left(j\times\sum_{i=1}^{n}\left[gcd(i,n)=j\right]\right)\) \(=\sum_{j=1}^{n}\left(j\times\sum_{i=1}^{n}\left[g

题解 Luogu P3370

讲讲这题的几种做法: 暴力匹配法 rt,暴力匹配,即把字符串存起来一位一位判相等 时间复杂度$ O(n^2·m) $ 再看看数据范围 $n\le10^5,m\le10^3$ 当场爆炸.当然有暴力分 代码(20pts): #include <bits/stdc++.h> using namespace std; char c[100001][1001]; bool pd(int x, int y) { int l1 = strlen(c[x]), l2 = strlen(c[y]); if(l1

题解 luogu P1501【[国家集训队]Tree II】(Link-Cut-Tree)

Link-Cut-Tree 的懒标记下传正确食用方法. 我们来逐步分析每一个操作. 1:+ u v c:将u到v的路径上的点的权值都加上自然数c; 解决方法: 很显然,我们可以 split(u,v) 来提取u,v这一段区间,提取完了将 Splay(v),然后直接在v上打加法标记add即可. 代码: inline void pushadd(ll x,ll val){//打标记 s[x]+=sz[x]*val,v[x]+=val,add[x]+=val; s[x]%=MOD,v[x]%=MOD,ad

[题解] luogu P1985 [USACO07OPEN]翻转棋

题面 今天学搜索,正好水一发以前做的这道毒瘤题 话说这道题做了我一天,别人都是各种优化,不超100行 天真的我硬核刚了220行,全程0优化水过 但其实不用这么长,我有的函数写的有点重复了( 思路: 显然是dfs,一行一行的来 搜到[i, j]时(i > 1),看[i - 1, j]是否为黑,是的话就翻转[i, j], 也就是说搜完当前行就要保证上一行的棋全都翻成了白色 当搜到最后一行时, 既要保证上一行翻成白色,还要保证自己也都翻成白色, 最后还要特判一下最后两个的翻转. 当时年少轻狂,我想着层

[题解] luogu P1514 引水入城 DFS + 贪心覆盖区间

原题链 打了一上午,我真是弱爆了 看完题目,可以很显然的想到一种搜法: DFS/BFS第1个到第m个临湖城市,求出干旱区城市能否全有水,很显然,这样时间会炸 此时,我们可以在选择搜第i个临海城市加一个剪枝: if (c[1][i-1] < c[1][i] && c[1][i+1] < c[1][i]) dfs(); 意思说,当且仅当 i 的前一个临湖城市和后一个临海城市的海拔都小于当前临湖城市海拔,我才去搜这个城市 为什么? 你会发现,一个临湖城市要是比自己相邻的临湖城市海拔小

[题解]luogu P2257 YY的GCD

袁野的gcd 首先可以肯定的是这是一道数论题 所以题目就是: \(\sum_{i=1}^N \sum_{j=1}^M [gcd(i,j)\in prime]\) 接下来就可以愉快的推式子了~ 首先可以按套路枚举 prime 和 gcd \(\sum_{p \in prime}\sum_{i=1}^N\sum_{j=1}^M[gcd(i,j)=p]\) 所以我们显然可以将\(i\)和\(j\) 同时除以 \(p\),得到 \(\sum_{p\in prime}\sum_{i=1}^{N}\sum_

题解 luogu P4415 【[COCI2006-2007#2] KOLONE】

一道纯模拟题. 将两队合并为一个字符串,用一个数组记录蚂蚁的方向,左队为0,右队为1.每一个时间点,两个两个字符地扫一遍字符串.由于0只能往右走,1只能往左走,所以只要在当前的两个字符中,第一个是0,第二个是1,就执行交换(同时交换方向数组和字符串),并且扫描指针加2.否则指针加1. 需要注意的是队伍合并时1左队顺序需要翻转. 自我感觉讲得比较明白了, 下贴代码: #include <bits/stdc++.h> using namespace std; int n1, n2, t, a[10