NOIP200003方格取数

NOIP200003方格取数
难度级别: D; 编程语言:不限;运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述

    XYZ 是首师大附中信息技术团编程大神之一,尤其近两个月水平提升迅猛,一发不可收拾。老师说他前途不可估量,于是他有一点小骄傲,好像没有什么题能难住他。这让另一位编程高手 WJH 看不下去了,于是要求出一道题考考他,如果 10 分钟内做不出来,以后不许再这么“嚣张”,XYZ 欣然同意。WJH 要求 XYZ 帮助 ZYT 解决一个问题:
    现有 N*N 的方格图( N<=10,将其中的某些方格中填入正整数,而其它的方格中放入数字 0。如下图所示(见样例): 
               
    ZYT 从图的左上角的 A 点出发,可以向下行走,也可以向右走,直到到达右下角的 B 点。在走过的路上,ZYT 可以取走方格中的数(取走后的方格中将变为数字 0 )。从 A 点到 B 点共走两次,你必须指导ZYT所取数之和最大。如果得到正确的最大值他可以找杨老师获得该最大数的 10 倍积分,如果最大值错误,要跑步的( ZYT 最怕这个,你知道的)。

输入
第一行为一个整数 N(表示N*N的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的0表示输入结束。
输出
只需输出一个整数,表示2条路径上取得的最大的和。
输入示例
8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0
输出示例
67
其他说明
数据范围:所有正整数都不会超过1000,太大了杨老师没那么多积分给的!

第一种方法是,我们可以使用费用流,对于每个点我们拆成两个点i,i`,并从i向i`连两条弧,容量均为1,一条费用为0,一条费用为-wi.

#include<cstdio>
#include<cctype>
#include<queue>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=210;
const int maxm=4010;
const int INF=1000000000;
struct ZKW {
    int n,m,s,t,first[maxn],next[maxm];
    int ans,cost;
    int vis[maxn],inq[maxn],d[maxn];
    struct Edge {int from,to,flow,cost;}edges[maxm];
    void init(int n) {
        this->n=n;m=0;
        memset(first,-1,sizeof(first));
    }
    void AddEdge(int from,int to,int cap,int cost) {
        edges[m]=(Edge){from,to,cap,cost};next[m]=first[from];first[from]=m++;
        edges[m]=(Edge){to,from,0,-cost};next[m]=first[to];first[to]=m++;
    }
    int BFS() {
        queue<int> Q;
        rep(i,1,n) d[i]=INF;
        d[t]=0;inq[t]=1;Q.push(t);
        while(!Q.empty()) {
            int x=Q.front();Q.pop();inq[x]=0;
            ren {
                Edge& e=edges[i^1];
                if(e.flow&&d[e.from]>d[x]+e.cost) {
                    d[e.from]=d[x]+e.cost;
                    if(!inq[e.from]) inq[e.from]=1,Q.push(e.from);
                }
            }
        }
        rep(i,0,m-1) edges[i].cost+=d[edges[i].to]-d[edges[i].from];
        cost+=d[s];return d[s]!=INF;
    }
    int DFS(int x,int a) {
        if(x==t||!a) {ans+=cost*a;return a;}
        int flow=0,f;vis[x]=1;
        ren {
            Edge& e=edges[i];
            if(e.flow&&!e.cost&&!vis[e.to]&&(f=DFS(e.to,min(a,e.flow)))) {
                flow+=f;a-=f;
                e.flow-=f;edges[i^1].flow+=f;
                if(!a) break;
            }
        }
        return flow;
    }
    int solve(int s,int t) {
        ans=cost=0;this->s=s;this->t=t;
        while(BFS()) do memset(vis,0,sizeof(vis));while(DFS(s,INF));
        return ans;
    }
}sol;
int n,w[15][15];
int id(int x,int y,int t) {return t*n*n+(x-1)*n+y;}
int main() {
    n=read();sol.init(n*n*2);
    while(1) {
        int x=read(),y=read(),v=read();
        if(!x) break;
        w[x][y]=v;
    }
    rep(i,1,n) rep(j,1,n) {
        sol.AddEdge(id(i,j,0),id(i,j,1),1,-w[i][j]);
        sol.AddEdge(id(i,j,0),id(i,j,1),1,0);
        if(i+1<=n) sol.AddEdge(id(i,j,1),id(i+1,j,0),1,0);
        if(j+1<=n) sol.AddEdge(id(i,j,1),id(i,j+1,0),1,0);
    }
    printf("%d
",-sol.solve(id(1,1,0),id(n,n,1)));
    return 0;
}
View Code

第二种方法是,我们使用DP。考虑一个人走两遍相当于两个人同时走,设f[x1][y1][x2][y2]表示第一个人走到了(x1,y1),第二个人走到了(x2,y2)最大收益。

转移时枚举上一次两个人在哪里,并加上这一步造成的收益:f[x1][y1][x2][y2]=Max(f[x1-1][y1][x2-1][y2],f[x1][y1-1][x2][y2-1],f[x1-1][y1][x2][y2-1],f[x1][y1-1][x2-1][y2])+w[x1][y1]+(x1!=x2||y1!=y2)*w[x2][y2].时间复杂度为O(N^4)

注意因为同时走,x1+y1恒等于x2+y2,可以将时间复杂度优化为O(N^3).

#include<cstdio>
#include<cctype>
#include<queue>
#include<cstring>
#include<algorithm>
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define dwn(i,s,t) for(int i=s;i>=t;i--)
#define ren for(int i=first[x];i!=-1;i=next[i])
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
const int maxn=15;
int n,w[maxn][maxn],f[maxn][maxn][maxn];
int max(int a,int b,int c,int d) {
    return max(max(a,b),max(c,d));
}
int dp(int x1,int y1,int x2) {
    if(x1==1&&y1==1) return w[1][1];
    int y2=x1+y1-x2;
    if(x1<1||x2<1||x1>n||x2>n||y1<1||y2<1||y1>n||y2>n) return -1<<30;
    int& ans=f[x1][y1][x2];
    if(ans>=0) return ans;
    int tmp=max(dp(x1-1,y1,x2-1),dp(x1,y1-1,x2-1),dp(x1,y1-1,x2),dp(x1-1,y1,x2));
    return ans=tmp+w[x1][y1]+(x1==x2?0:1)*w[x2][y2];
}
int main() {
    memset(f,-1,sizeof(f));
    n=read();
    while(1) {
        int x=read(),y=read(),v=read();
        if(!x) break;
        w[x][y]=v;
    }
    printf("%d
",dp(n,n,n));
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/wzj-is-a-juruo/p/4695111.html