1. 程式人生 > >【重慶市NOIP模擬賽】資料

【重慶市NOIP模擬賽】資料

資料
時間限制: 1 Sec 記憶體限制: 128 MB
提交: 58 解決: 31
[提交][狀態][我的提交]
題目描述
Mr_H 出了一道資訊學競賽題,就是給 n 個數排序。輸入格式是這樣的:
試題有若干組資料。每組資料的第一個是一個整數 n,表示總共有 n 個數待排序;接下來 n 個整數,分別表示這 n 個待排序的數。
例如:3 4 2 –1 4 1 2 3 4,就表示有兩組資料。第一組有 3 個數(4,2,-1),第二組有 4個數(1,2,3,4)。可是現在 Mr_H 做的輸入資料出了一些問題。例如:2 1 9 3 2 按理說第一組資料有 2 個數(1,9),第二組資料有 3 個數,可是“3”後面並沒有出現三個數,只出現了一個數“2”而已!
現在 Mr_H 需要對資料進行修改,改動中“一步”的含義是對檔案中的某一個數+1 或-1,寫個程式,計算最少需要多少步才能將資料改得合法。

輸入
第一行一個整數 m,表示 Mr_H 做的輸入資料包含的整數個數。第二行包含 m 個整數 a[i],每個整數的絕對值不超過 10000。

輸出
一個整數,表示把資料修改為合法的情況下,最少需要多少步。

樣例輸入
Copy (如果複製到控制檯無換行,可以先貼上到文字編輯器,再複製)

4
1 9 3 2
樣例輸出
2
提示
對於 20%的資料,m<=10, |a[i]|<=5;

對於 60%的資料,m<=5000, |a[i]|<=10000

對於 100%的資料,m<=100000, |a[i]|<=10000

法一:圖論。

  • 每個點i可以零損耗地到達第i+a[i]+1個點,所以連一條權值為零的單向邊。
  • 同時,每個點可以通過加減來左移或右移,而每移動一次就是修改一次,所以從每個點向兩邊相鄰的點連一條權值為一的邊。
  • 最後答案就是從1到n+1的最短路徑。

注意三點:
1. 點1如果為負數要先把它變為0再連邊
2. 點1不向兩邊連,點2不向左連
3. 連向的點可能超過n+1,也是合法的

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int n,a,nn,dis[110005],u,v,ans;
queue<int>q;
bool inq[110005];
int
fir[110005],nxt[660010],to[660010],w[660010],tot; void line(int x,int y,int z) { nxt[++tot]=fir[x]; fir[x]=tot; to[tot]=y; w[tot]=z; } void spfa() { memset(dis,0x3f,sizeof dis); dis[1]=0; q.push(1); while(!q.empty()) { u=q.front(); for(int i=fir[u];i;i=nxt[i]) { v=to[i]; if(dis[v]>dis[u]+w[i]) { dis[v]=dis[u]+w[i]; if(!inq[v]) inq[v]=1,q.push(v); } } inq[u]=0; q.pop(); } } int main() { scanf("%d",&n); scanf("%d",&a); if(a<0) ans=-a,line(1,2,0); else line(1,a+2,0); nn=max(nn,a+2); for(int i=2;i<=n;i++) { scanf("%d",&a); if(a>=0) line(i,i+a+1,0); nn=max(nn,i+a+1); line(i,i+1,1); line(i+1,i,1); } for(int i=n+1;i<nn;i++) line(i,i+1,1),line(i+1,i,1); spfa(); printf("%d",dis[n+1]+ans); }

法二 DP的(優先佇列)堆優化

設f[i]表示把前i個數合法化需要的最小操作次數,則:
f[i]=f[j]+abs(a[j+1]-(i-j-1));{0<=j< i, 1<=i<=n;}

這裡的abs(a[j+1]+j+1-i)便是整個題最磨人的地方了。

儘管我們很容易想到分類討論,但卻發現兩個嚴肅的問題:

  • a[j+1]可能為負數,所以a[j+1]+j+1並不具有嚴格的增減性,不能使用單調佇列優化

  • 當i< a[j+1]+j+1時, 我們不知道什麼時候該pop,因為i在遞增,此時不滿足的a[j+1]+j+1可能以後就滿足了。

so,我們採用了一種巧妙的優化。見程式碼:

#include<cstdio>
#include<queue>
#include<cctype>
using namespace std;
struct node
{
    int x,y;
    node(){}
    node(int a,int b){x=a;y=b;}
    bool operator < (const node& p)const{return x>p.x;}
};
priority_queue<node>q;
int n,a[100005],minx=10000000,f[100005];
inline void get(int &a)
{
    char c;a=0;int f=1;
    while(!isdigit(c=getchar())) if(c=='-') f=-1;
    while(isdigit(c)) a=a*10+c-'0',c=getchar();
    a*=f;
}
int main()
{
    get(n);
    for(int i=1;i<=n;i++) get(a[i]);
    q.push(node(a[1]+1,a[1]+1));
    for(int i=1;i<=n;i++)
    {
        while(!q.empty()&&q.top().y<=i)
        {
            minx=min(q.top().x-2*q.top().y,minx);
            q.pop();
        }
        f[i]=minx+i;
        if(!q.empty()) f[i]=min(f[i],q.top().x-i);
        q.push(node(f[i]+a[i+1]+i+1,a[i+1]+i+1));
    }
    printf("%d",f[n]);
}

pay attention to the rule!!!
這個優先佇列並不是按照a[i+1]+i+1來排序的,而是f[i]+a[i+1]+i+1!!!
首先,對於a[j+1]+j+1<=i的情況,因為前面的i滿足,後面的i肯定也滿足,所以直接用一個minx來儲存,然後直接pop。
而對於a[j+1]+j+1>i的情況,直接取最優的f[j]+a[j+1]+j+1則可。

但是,同志們可能會發現一個問題,既然是按照f[i]+a[i+1]+i+1排序,
那麼如果在a[j+1]+j+1>i時直接退出,那麼如果後面還有滿足a[j+1]+j+1<=i的值怎麼辦呢?

這就是巧妙之處了。

令f[j]=a,a[j+1]+j+1=b;當b>i時記作a1,b1;當b<=i時記作a2,b2
那麼我們的f[i]=min(a2-b2+i,a1+b1-i);
這裡我們取了最優的a1+b1-i,那麼我們如何說明後面的a2-b2+i不會比現在更優呢?
注意優先佇列的規則,我們得知:a1+b1<=a2+b2
而我們已知:b2<=i
所以 2 * b2 <= 2 * i
所以 b2 <= 2*i-b2
所以a2+b2<=a2+2*i-b2
所以 a1+b1<=a2+b2<=a2-b2+2*i
所以 a1+b1-i<=a2-b2+i !!!!!!!
證明至此,我們可知後面的解不會比當前更優,直接退出。

法三:DP的線段樹優化

眾所周知,對於這種含絕對值的DP,線段樹是最在行(難寫)的了,(基本上就沒寫對過)。。。。鑑於本人目前對於線段樹優化一知半解。。
這裡。。。就不加解析了。。。等孤學成歸來在好好補上吧。。這裡就直接上程式碼了

#include<iostream>
#include<cstring>
#include<cstdio>
#define LCH(i) (2 * i)
#define RCH(i) (2 * i + 1)
const int maxn = 100000;
const int  inf = 1 << 30;
using namespace std;

struct Tree{
    int lc, rc, fgr[2];
}tree[maxn * 4];

int n, num[maxn + 5], pos[maxn + 5], f[maxn + 5];

void built(int i, int l, int r)
{
    tree[i].lc = l, tree[i].rc = r;
    tree[i].fgr[0] = tree[i].fgr[1] = inf;
    if(l == r){
        pos[l] = i;
        return;
    }
    int mid = (l + r) / 2;
    built(LCH(i), l, mid);
    built(RCH(i), mid + 1, r);
}

void update(int i, int val, bool f)
{
    tree[i].fgr[f] = min(tree[i].fgr[f], val);
    while(i != 1){
        i /= 2;
        tree[i].fgr[f] = min(tree[i].fgr[f], val);
    }
}

int query(int i, int l, int r, bool f)
{
    if(tree[i].lc > r || tree[i].rc < l) return inf;
    if(tree[i].lc >= l && tree[i].rc <= r)
        return tree[i].fgr[f];
    return min(query(LCH(i), l, r, f), query(RCH(i), l, r, f));
}

int main()
{
    memset(f, 0x3f3f3f3f, sizeof f);
    scanf("%d", &n);
    built(1, 1, n + 1);
    for(int i = 1; i <= n; i ++)
        scanf("%d", &num[i]);
    num[++ n] = 0;
    f[n] = num[n];
    update(pos[n], f[n] + n, 0);
    update(pos[n], f[n] - n, 1);
    for(int i = n - 1; i >= 1; i --){
        int t = i + 1 + num[i];
        if(t >= n){
            t = tree[1].fgr[1];
            f[i] = i + 1 + num[i] + t;
            update(pos[i], f[i] + i, 0);
            update(pos[i], f[i] - i, 1);
            continue;
        }
        t = query(1, i + 1 + num[i], n, 0);
        f[i] = min(f[i], t - (i + 1 + num[i]));
        t = query(1, i + 1, i + 1 + num[i], 1);
        f[i] = min(f[i], i + 1 + num[i] + t);
        update(pos[i], f[i] + i, 0);
        update(pos[i], f[i] - i, 1);
    }
    printf("%d", f[1]);
}

Just do it!