1. 程式人生 > >[CQOI2012]交換棋子

[CQOI2012]交換棋子

時也 生效 col oid include AI int main HA

~~~題面~~~

題解:

其實還算是道好題

一開始很快想出了一個接近正解的建圖方法,但其實是錯誤的,不過還是騙了70分_(:зゝ∠)_

首先我們可以觀察到棋子有限,但費用多種,其實也就相當於限制了流量,找最小費用

對於初始狀態的每一個1,我們連s ---> x flow = 1 cost = 0

對於目標狀態的每一個1,我們連x ---> t flow = 1 cost = 0

對於每一個方塊,我們向周圍八個格子連邊 flow = inf , cost = 1(表示交換了一次)

然後就是比較妙的部分了

首先我們要拆點,因為每個點有流量限制(交換次數)

我們考慮以下三種情況

1,初始狀態是0, 目標狀態也是0, 我們連x ----> x‘ flow = limit/2 cost = 0

  那麽為什麽連的是limit/2,而不是limit呢?

  我們可以觀察到對於這樣一個路徑1 ----> x----> 3,夾在中間的x被翻轉了兩次,但流量卻只流經了一次,

  所以流一次實際是消耗了2的限制,因此我們直接在建邊的時候就建limit/2

2,初始狀態是1, 目標狀態是0, 我們連x ---> x‘ flow = (limit + 1) / 2 cost = 0;

  那麽為什麽這裏又要+1呢?

  因為這裏本來就有個棋子,但目標狀態卻沒有,因此這個棋子是必須換出去的。

  但是這種交換又和上面的不同了,因為這種情況下,點x在路徑中的位置是一個端點,因此是這樣的x ----> 2,

  於是我們觀察到這條路徑上,x並沒有被翻轉兩次,流量卻和上面一樣流經了一次,也就是說如果不做任何修改,

  這樣雖然只翻轉了一次,卻還是在被當做翻轉了2次對待。這顯然是不合理的。

  就比如這樣的情況:

    x ----> 2 其中x的limit是1,那麽這個時候x上的那個棋子顯然是可以轉一次就轉出去的,但是如果直接按偶數建就把這次機會給忽略掉了。

3,初始狀態是0, 目標狀態是1, 我們連x ---> x‘ flow = (limit - 1) / 2 cost = 0;

  為什麽這裏是-1?

  因為這裏是別的棋子要進來,進來後因為是目標位置,所以就直接去t了,不會流經x ---> x‘

  但這顯然也是要浪費一個機會的,因此我們在開頭就減掉這個1.

總的來說就是用1 的流量表示2個翻轉次數,如果流一次會導致多統計1,那我們就在開頭加一個1補回來多余的消耗

如果流一次會導致統計不到需要統計的那個1,那我們就在開頭-1來表示機會被消耗掉了一個

其實應該也算是人類智慧的一種體現吧,,,強行分類嘛。

這裏有一個很巧妙的實現:建邊的時候直接給limit加上in[i][j] - out[i][j],具體為什麽想想就知道啦,不過我覺得這是個巧合emmmm

不過貌似還有另一種解法,拆3個點,中間的邊這樣連:

x ---> x‘ flow = limit/2 , cost = 0

x‘ ---> x‘_ flow = limit/2 + (limit % 2 ? 1 : 0)

當然這只是我的口胡,,,具體正確性有待考證

附代碼(有非常詳細的註釋,,,當然也有一些亂七八糟的註釋,,,不過其實我覺得直接看代碼應該也能懂_(:зゝ∠)_)

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define inf 2139062143
  5 #define AC 6000
  6 #define ac 80000
  7 int n, m, s, all, t, ans, ansflow, cnt, tmp;
  8 int date[ac], Head[AC], Next[ac], haveflow[ac], cost[ac], tot = 1;
  9 int dis[AC], disflow[AC], last[AC], in[30][30], out[30][30];
 10 int a[11] = {-1, 1, 0, 0, -1, -1, 1, 1}, b[11] = {0, 0, -1, 1, 1, -1, 1, -1}; 
 11 bool z[AC];
 12 deque<int> q;
 13 char ss[30][30];
 14 /*因為原來有,後來沒有的格子必須要有一次是經過一次翻轉然後出去的,因此對於這種,應該要加1的限制,
 15 而原來沒有,後來有的格子,必須要有一次是經過一次翻轉得到一個棋子的,因此對於這種,應該要-1的限制,
 16 這兩種之所以不同就是因為原來有,現在沒有的格子是要出去棋子,這種情況下肯定會消耗一個流量,且不會有多消耗的風險,
 17 而原來沒有,後來有的格子,因為是從別的格子進來的,因此無法分辨這到底是要停下的流量,還是要出去的流量,
 18 而且完全可能本來是到這裏停下的流量跑了出去。這時如果還保留這一個流量,就可能會造成一個點可以翻轉兩次,
 19 但別的棋子進來那次本來就去掉了一次了,卻沒有在這上面體現出來,因為流量到了這個點就直接去t了,
 20 根本不會被計入x ---> x‘的管道中。
 21 簡單來說就是第一種肯定且僅會產生1的流量,而且這個流量將被計入x ---> x‘中,因此我們就要多分配1的限制去保證奇數時也可以生效。
 22 而第二種肯定且僅會產生1的流量,但這個流量將不會被計入x ---> x‘中,但是實際上這個邊應當要統計到它,因為它也消耗了一次翻轉限制。
 23 所以就要人為的減去這個限制,以防止偶數時這次流入打破了偶數的條件,卻依然按照偶數來跑*/
 24 inline void add(int f, int w, int S, int C)
 25 {
 26     date[++tot] = w, Next[tot] = Head[f], haveflow[tot] = S, cost[tot] = C, Head[f] = tot;
 27     date[++tot] = f, Next[tot] = Head[w], cost[tot] = -C, Head[w] = tot;
 28 //    printf("%d ---> %d %d %d\n", f, w, S, C);    
 29 }
 30 
 31 inline int id(int x, int y)
 32 {
 33     return (x - 1) * m + y;
 34 }
 35 //error!!!流進來一次,流出去一次,一共消耗了兩個流量!
 36 //所以一共塊進來最多limit/2次,出去最多limit/2 + 1(單數的話)次(因為不用流進來的消耗)
 37 //因此要拆成3個點。,。。。
 38 void pre()
 39 {
 40     int x;
 41     scanf("%d%d", &n, &m);
 42     all = n * m;
 43     s = all * 2 + 1, t = s + 1;
 44     for(R i = 1; i <= n; i++)
 45     {
 46         scanf("%s", ss[i] + 1);
 47         for(R j = 1; j <= m; j++)
 48         {
 49             x = id(i, j);
 50             in[i][j] = ss[i][j] - 0;
 51             if(ss[i][j] - 0 > 0) add(s, x, 1, 0), ++tmp;
 52             for(R k = 0; k <= 7; k++)
 53                 if(i + a[k] > 0 && i + a[k] <= n && j + b[k] > 0 && j + b[k] <= m) //8個格子都要連
 54                     add(x + all, id(i + a[k], j + b[k]), inf, 1);//1 ---> 2 ---> 3這樣的路徑實際上只有兩次翻轉,而中間的點將失去兩次機會 
 55         }//因此只需要在1 ---> 2   2 ---> 3這樣的路徑中記錄cost表示一次翻轉就可以了,中間流量限制為limit/2即可(因為失去了兩次機會)
 56     }
 57     for(R i = 1; i <= n; i++)
 58     {
 59         scanf("%s", ss[i] + 1);
 60         for(R j = 1; j <= m; j++)
 61         {
 62             x = id(i, j);
 63             out[i][j] = ss[i][j] - 0;
 64             if(ss[i][j] - 0 > 0) 
 65                 add(x, t, 1, 0), ++cnt;
 66                 //只有原來有,現在沒有,也就是要強制移走的時候,才應該給一次機會,多給一個流量讓它流走
 67             //因為只有這個時候才會出現一條只有兩個點的路徑,即整條路徑上的點都只消耗一的流量,所有點都是端點
 68         }//因為當需要在此格停下的時候,不用穿過去實現再一次翻轉,從而浪費一些流量,因此連向t的應當是原來的點,而不是拆出來的點
 69     }
 70     if(cnt != tmp)
 71     {
 72         printf("-1\n");
 73         exit(0);
 74     }
 75     for(R i = 1; i <= n; i++)
 76     {
 77         scanf("%s", ss[i] + 1);
 78         for(R j = 1; j <= m; j++)
 79         {
 80             x = id(i, j);
 81                tmp = ss[i][j] - 0;
 82                tmp += in[i][j] - out[i][j];//通過觀察可以發現這樣的關系所需要的改動剛好就是in[i][j] - out[i][j]的結果
 83             if(tmp / 2 > 0) add(x, x + all, tmp / 2, 0);
 84         }
 85     }
 86 }
 87 
 88 void aru()
 89 {
 90     int x = t;
 91  //   printf("%d ", t);
 92     while(x != s)
 93     {
 94         haveflow[last[x]] -= disflow[t];
 95         haveflow[last[x] ^ 1] += disflow[t];
 96         x = date[last[x] ^ 1];
 97   //      printf("<--- %d ",x);
 98     }
 99   //  printf("  cost = %d\n", dis[t]);
100     ans += disflow[t] * dis[t];
101     ansflow += disflow[t];
102 }
103 
104 bool spfa()
105 {
106     int x, now;
107     disflow[s] = inf, dis[s] = 0;
108     q.push_front(s), z[s] = true;
109     while(!q.empty())
110     {
111         x = q.front();
112         q.pop_front();
113         z[x] = false;
114         for(R i = Head[x]; i ; i = Next[i])
115         {
116             now = date[i];
117             if(haveflow[i] && dis[now] > dis[x] + cost[i])
118             {
119                 dis[now] = dis[x] + cost[i];
120                 disflow[now] = min(disflow[x], haveflow[i]);
121                 last[now] = i;
122                 if(!z[now] && now != t)//error!!!now不能等於t
123                 {
124                     z[now] = true;
125                     if(!q.empty() && dis[now] < dis[q.front()]) q.push_front(now);
126                     else q.push_back(now);
127                 }
128             }
129         }
130     }
131     if(dis[t] != inf) aru();
132 //    printf("!!!%d\n", ans);
133     return dis[t] != inf;
134 }
135 
136 void work()
137 {
138     memset(dis, 127, sizeof(dis));
139     while(spfa())    memset(dis, 127, sizeof(dis));
140     if(ansflow >= cnt) printf("%d\n", ans);
141     else printf("-1\n");
142 }
143 
144 int main()
145 {
146     freopen("in.in","r",stdin);
147     pre();
148     work();
149     fclose(stdin);
150     return 0;
151 }

[CQOI2012]交換棋子