1. 程式人生 > >ccf 201809-4 再賣菜

ccf 201809-4 再賣菜

題意:

問題描述
  在一條街上有n個賣菜的商店,按1至n的順序排成一排,這些商店都賣一種蔬菜。
  第一天,每個商店都自己定了一個正整數的價格。店主們希望自己的菜價和其他商店的一致,第二天,每一家商店都會根據他自己和相鄰商店的價格調整自己的價格。具體的,每家商店都會將第二天的菜價設定為自己和相鄰商店第一天菜價的平均值(用去尾法取整)。
  注意,編號為1的商店只有一個相鄰的商店2,編號為n的商店只有一個相鄰的商店n-1,其他編號為i的商店有兩個相鄰的商店i-1和i+1。
  給定第二天各個商店的菜價,可能存在不同的符合要求的第一天的菜價,請找到符合要求的第一天菜價中字典序最小的一種。
  字典序大小的定義:對於兩個不同的價格序列(a1, a2, …, an)和(b1, b2, b3, …, bn),若存在i (i>=1), 使得ai<bi,且對於所有j<i,aj=bj,則認為第一個序列的字典序小於第二個序列。
輸入格式
  輸入的第一行包含一個整數n,表示商店的數量。
  第二行包含n個正整數,依次表示每個商店第二天的菜價。
輸出格式
  輸出一行,包含n個正整數,依次表示每個商店第一天的菜價。
樣例輸入


8
2 2 1 3 4 9 10 13
樣例輸出
2 2 2 1 6 5 16 10
資料規模和約定
  對於30%的評測用例,2<=n<=5,第二天每個商店的菜價為不超過10的正整數;
  對於60%的評測用例,2<=n<=20,第二天每個商店的菜價為不超過100的正整數;
  對於所有評測用例,2<=n<=300,第二天每個商店的菜價為不超過100的正整數。
  請注意,以上都是給的第二天菜價的範圍,第一天菜價可能會超過此範圍。

分析及思路:

這道題是非常經典的最短路解差分約束問題,然而考試的時候發了草稿紙卻沒有拿筆,沒看出來一開始還以為考的dp,索性直接暴力dfs一波,然而dfs也寫搓了,只有30分,回來拿筆一列,我擦,居然是差分約束。
先講講差分約束為什麼可以用最短路求:


首先,只有二元一次不等式建立的差分約束才能用最短路求解,因為圖中的兩個點是一一對應建立關係的。
舉個例子,對於xj-xi≤k,這樣的不等式非常像最短路的鬆弛操作,假設我們有兩個點,i和j,在最短路中會有轉移方程if(dist[j]-dist[i]>k),dist[j]=dist[i]+k;,看出來了吧,當前i到j有一條邊權值為k,這是相當於對j更新,原來的dist[j]不滿足約束條件,也就是dist[j]-dist[i]>k,更新後一定滿足約束條件,更新後的dist[j]就滿足dist[j]-dist[i]≤k,dist[i]就相當xi,也就是xj-xi≤k!而且這個更新後得到的序列是字典序最大的,為什麼?很簡單,因為當dist[j]-dist[i]>k時讓dist[j]-dist[i]≤k,dist[j]可以更新到dist[i]+k-n,n可以取任意正整數都滿足條件,但這裡的更新就相當於n取零,使得dist[j]有最大值。
然而這道題要求字典序最小,怎麼辦呢?很簡單,我們把所有的不等式都變成xi-xj≥k,反過來求最長路即可,跟最短路的原理是一模一樣的,這裡不多論述關於差分約束的講解,網上有大量教程。
接下來講如何建圖的問題:

首先這裡的不等式是這樣的:
假設n=5,則有:

  1. a1* 2≤ x1+x2 ≤ a1* 2+1
  2. a2* 3≤ x1+x2+x3 ≤ a2* 3+2
  3. a3* 3≤ x2+x3+x4 ≤ a3* 3+2
  4. a4* 3≤ x3+x4+x5 ≤ a4* 3+2
  5. a5* 2≤ x4+x5 ≤ a5* 2+1
  6. x1≥1
  7. x2≥1
  8. x3≥1
  9. x4≥1
  10. x5≥1
    xi為第一天第i家店的售價,ai為第二天第i家點的售價。
    不難發現存在3元一次不等式怎麼辦呢?我們可以引入中間變數si=x0+x1+…+xi,這裡的x0等於0;
    即可將上面的第一個不等式轉化成: a1* 2≤ s2-s0 ,a1* 2+1≤s0-s2.其它的也一樣,不一一列舉,全部轉化成大於等於的形式,sj-si≥k,i到j建立一條權值為k的單向邊,然後spfa跑最長路即可,這裡的spfa與原來的spfa有些區別,在spfa之前記得將所有點加入佇列,並且dist置0,因為差分約束系統不保證圖是連通的,所以要先建立一個虛擬節點,這個虛擬節點為源點,向所有邊連線一條權值為0的有向邊,以上的操作便是人工省略了虛擬源點的步驟。
    有關於差分約束的更多問題可以上網找部落格看。
    提交官網結果:
    在這裡插入圖片描述
    AC程式碼:
#include<iostream>
#include<queue>
#include<cstring>
#define rep(i,x,n) for(int i=x;i<n;i++)
using namespace std;
//head
struct Edge{int to,next,v;}edge[2006];
int n,cur=0,a[306],head[306],dist[306],inq[306],vis[306];
void addedge(int x,int y,int v){edge[cur].to=y;edge[cur].next=head[x];edge[cur].v=v;head[x]=cur++;}
void spfa()
{
     queue<int>qq;
     rep(i,0,n+1)
     {qq.push(i);vis[i]=1;dist[i]=0;inq[i]=1;}
     while(!qq.empty())
     {
         int x=qq.front();
         qq.pop();
         inq[x]++;
         vis[x]=0;
         if(inq[x]>n){cout<<"noanswer"<<endl;return ;}
         for(int i=head[x];i!=-1;i=edge[i].next)
         {
             int nx=edge[i].to;
             if(dist[nx]<dist[x]+edge[i].v)
             {
                 dist[nx]=dist[x]+edge[i].v;
                 if(!vis[nx])
                 {
                     vis[nx]=1;
                     qq.push(nx);
                 }
             }
         }
     }
     return ;
}
int main()
{
     ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
     memset(head,-1,sizeof(head));
     cin>>n;
     rep(i,1,n+1)cin>>a[i];
     rep(i,0,n-2){addedge(i+3,i,-(a[i+2]*3+2));addedge(i,i+3,a[i+2]*3);}
     addedge(2,0,-(a[1]*2+1));addedge(0,2,a[1]*2);   //對開始兩個單獨處理
     addedge(n,n-2,-(a[n]*2+1));addedge(n-2,n,a[n]*2);  //對結尾兩個單獨處理
     rep(i,1,n+1)addedge(i-1,i,1);              //每個數都要大於等於1
     spfa();
     a[1]=dist[1];
     rep(i,2,n+1)a[i]=dist[i]-dist[i-1];
     cout<<a[1];rep(i,2,n+1)cout<<' '<<a[i];
     return 0;
}