[NOIP2008]传纸条

先用双线动规写一遍,下午再来写费用流解法= =

解法1:双线动规

     首先我们不难看出,由于纸条回来时不能与去时的路径重叠,“一来一回”和“从左上角分两条路走向右下角”这两个模型是等价的。于是我们可以把两条路到达的端点同时作为状态保存下来(dp[x1][y1][x2][y2])。又因为矩阵图的特殊性,左上角到右下角的所有路径长度均为两点的曼哈顿距离,我们可以让两点”同时“移动,即任何时刻两点走过的路程相同。这样,我们可以记当前状态为dp[i, j, k],其中 i 表示当前两点走到的横纵坐标之和,j表示第一条路径走到的横坐标,k表示第二条路径走到的横坐标。考虑到两条路径在途中不能重叠,我们约定j > k。其中每个位置最多都可以由两个点达到,那么每种状态最多要考虑2*2=4种前驱。这里要考虑一种特殊情况:当k == j-1时,两点都可以由(k, i-k-1)这一点走到,然而题目中规定路径中不能有重叠,那么这时我们应当排除”两点从同一点转移得到“的情况╮(╯▽╰)╭

    这样,这道题就完美解决了→_→时间复杂度为O((M+N)MN)

    p.s.截至发表前,这个解法在河南省实验中学COGS上排到了速度rank1→_→ (5ms)

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <iostream>
 4 #include <cctype>
 5 #include <cmath>
 6 #define maxn (52)
 7 using namespace std;
 8 #ifdef DEBUG
 9 FILE *in = fopen("test","r");
10 #define out stdout
11 #else
12 FILE *in = fopen("message.in","r");
13 FILE *out = fopen("message.out","w");
14 #endif
15 
16 inline void getint(int &k){
17     char c = fgetc(in);
18     while(!isdigit(c))c = fgetc(in);
19     k = c - '0';
20     while(isdigit(c = fgetc(in)))
21         k = k * 10 + (c - '0');
22 }
23 int m, n;
24 int Mat[maxn][maxn];//坐标从1开始
25 int dp[maxn<<1][maxn][maxn] = {0};
26 int main(){
27     int i, j, k, Max, t;
28     getint(m),getint(n);
29     for(i = 1;i <= m;++i)
30         for(j = 1;j <= n;++j)getint(Mat[i][j]);
31     dp[3][2][1] = Mat[2][1] + Mat[1][2];
32     for(i = 4;i < m+n;++i)
33         for(j = 2;j <= m;++j){
34             if(j == i)break;
35             for(k = max(1,i-n);k < j;++k){
36                 Max = dp[i-1][j][k];
37                 if((t=dp[i-1][j][k-1]) > Max)Max = t;
38                 if((t=dp[i-1][j-1][k-1]) > Max)Max = t;
39                 if(k!=j-1 && (t=dp[i-1][j-1][k])>Max)Max = t;
40                 dp[i][j][k] = Max + Mat[j][i-j] + Mat[k][i-k];
41             }
42         }
43     i = m + n - 1;
44     fprintf(out,"%d\n",dp[i][m][m-1]);
45     return 0;
46 }
双线动规

 解法2:最大费用最大流

    好吧现在已经是第二天了……感觉费用流写起来很费时间啊QAQ……

    这道题的建模思路是这样的……将每个学生抽象成一条有向边,纸条抽象为流量,好心值抽象为费用,考虑以下两点限制条件:1.最多为2的流量流经(1,1)和(m,n)两学生;2.中间每个学生对应的容量为1(每个学生只会传一次纸条);3.在相邻的两个学生中,从偏左、偏上的学生对应边的终点向另一学生对应边的起点连边,容量为1,费用为0;4.源点为(1,1)学生的起点,汇点为(m,n)学生的终点。

    对这样一个有向网络建图,求出最大费用最大流,即为原问题的解。由于问题的特殊性,spfa运行次数接近常数,因此这一算法的时间复杂度接近最短路。(然而,对于这样小的网格,费用流的解法其实有些浪费……)STL容器的时间常数似乎有点大,这个解法在COGS上花了9毫秒(而且是打开了-O2优化……醉了醉了)

 

  1 #include <cstdio>
  2 #include <algorithm>
  3 #include <iostream>
  4 #include <cctype>
  5 #include <cmath>
  6 #include <vector>
  7 #include <queue>
  8 #include <deque>
  9 #include <ctime>
 10 #define Vect vector<edge*>
 11 #define pb push_back
 12 #define iter(v) v::iterator
 13 #define bg begin()
 14 #define maxn (52)
 15 #define maxv (5002)
 16 using namespace std;
 17 
 18 #if defined DEBUG
 19 FILE *in = fopen("test","r");
 20 #define out stdout
 21 #else
 22 FILE *in = fopen("message.in","r");
 23 FILE *out = fopen("message.out","w");
 24 #endif
 25 
 26 inline void getint(int &k){
 27     char c = fgetc(in);
 28     while(!isdigit(c))c = fgetc(in);
 29     k = c - '0';
 30     while(isdigit(c = fgetc(in)))
 31         k = k * 10 + (c - '0');
 32 }
 33 
 34 struct edge{
 35     int w, vol, to;
 36     edge(){}
 37     edge(int W, int V, int T):w(W),vol(V),to(T){}
 38 }E[maxv*6];
 39 int preE[maxv];
 40 int Ecnt = 0;
 41 Vect adj[maxv];
 42 int m, n, Vnum = 2, preV[maxv], ans = 0;
 43 inline void addE(int &Ecnt, int f, int t, int w, int v){
 44     E[Ecnt] = edge(w,v,t);
 45     adj[f].pb(E + Ecnt++);
 46     E[Ecnt] = edge(-w,0,f);
 47     adj[t].pb(E + Ecnt++);
 48 }
 49 
 50 inline void init(){
 51     int i, j, w;
 52     getint(w);
 53     addE(Ecnt, 01, w, 2);
 54     for(i = 1;i < n; ++i){
 55         getint(w);
 56         addE(Ecnt, Vnum-1, Vnum, 01);
 57         addE(Ecnt, Vnum, Vnum+1, w, 1);
 58         Vnum += 2;
 59     }
 60     for(i = 1;i < m-1; ++i){
 61         getint(w);
 62         addE(Ecnt, Vnum-(n<<1)+1, Vnum, 01);
 63         addE(Ecnt, Vnum, Vnum+1, w, 1);
 64         Vnum += 2;
 65         for(j = 1;j < n; ++j){
 66             getint(w);
 67             addE(Ecnt, Vnum-1, Vnum, 01);
 68             addE(Ecnt, Vnum-(n<<1)+1, Vnum, 01);
 69             addE(Ecnt, Vnum, Vnum+1, w, 1);
 70             Vnum += 2;
 71         }
 72     }
 73     getint(w);
 74     addE(Ecnt, Vnum-(n<<1)+1, Vnum, 01);
 75     addE(Ecnt, Vnum, Vnum+1, w, 1);
 76     Vnum += 2;
 77     for(j = 1;j < n; ++j){
 78         getint(w);
 79         addE(Ecnt, Vnum-1, Vnum, 01);
 80         addE(Ecnt, Vnum-(n<<1)+1, Vnum, 01);
 81         if(j < n-1)addE(Ecnt, Vnum, Vnum+1, w, 1);
 82         else addE(Ecnt, Vnum, Vnum+102);
 83         Vnum += 2;
 84     }
 85 }
 86 bool spfa(int &d){
 87     int dis[maxv] = {0}, tmp, t;
 88     bool inq[maxv] = {0}, known[maxv] = {1};
 89     queue<int> Q;
 90     iter(Vect) it;
 91     inq[0] = 1, Q.push(0);
 92     while(!Q.empty()){
 93         tmp = Q.front(), Q.pop(), inq[tmp] = 0;
 94         it = adj[tmp].begin();
 95         for(;it != adj[tmp].end();++it){
 96             if((*it)->vol <= 0)continue;
 97             t = dis[tmp] + (*it)->w;
 98             if(t < 0)continue;
 99             if(!known[(*it)->to] || t > dis[(*it)->to]){
100                 dis[(*it)->to] = t;
101                 known[(*it)->to] = 1;
102                 preE[(*it)->to] = *it - E;
103                 preV[(*it)->to] = tmp;
104                 if(!inq[(*it)->to])inq[(*it)->to] = 1, Q.push((*it)->to);
105             }
106         }
107     }
108     d = dis[Vnum-1];
109     if(known[Vnum-1])return 1;
110     else return 0;
111 }
112 inline void aug(int &dis){
113     int k = Vnum - 1;
114     ans += dis;
115     while(k != 0){
116         E[preE[k]].vol -= 1;        
117         E[preE[k]^1].vol += 1;
118         k = preV[k];
119     }
120     dis = 0;
121 }
122 int main(){
123     getint(m),getint(n);
124     init();
125     int dis = 0;
126     while(spfa(dis))
127         aug(dis);
128     fprintf(out,"%d\n",ans);
129     #if defined DEBUG
130     cout << (double)clock() / CLOCKS_PER_SEC;
131     #endif
132     return 0;
133 }
最大费用最大流


posted @ 2014-10-06 11:39  Asm.Definer  阅读(642)  评论(0编辑  收藏  举报