【题】【(堆/线段树/树状数组优化DP)/图论】NKOJ 3485 数据

26 篇文章 0 订阅
16 篇文章 0 订阅

NKOJ 3485 数据
时间限制 : 30000 MS 空间限制 : 165536 KB

问题描述
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。

输出格式
一个整数,表示把数据修改为合法的情况下,最少需要多少步。

样例输入
【样例输入1】
4
1 9 3 2

【样例输入2】
10
4 4 3 5 0 -4 -2 -1 3 5

样例输出
【样例输出1】
2

【样例输出2】
3

提示
【数据范围】
对于 20%的数据,m<=10, |a[i]|<=5;
对于60%的数据,m<=5000, |a[i]|<=10000
对于100%的数据,m<=100000, |a[i]|<=10000

来源 【2015多校联训4】 by YZ

法一,图论:
因为a[i]>=0时,i可以无损耗到达i+a[i]+1号节点,所以连一条权值为0单向边,注意终点可能超过n+1号,也是合法的。
因为一个节点到达k号后,往左或往右一格均视为操作一次,所以各个节点向左右连一条双向边,注意包括超过n+1的点。且第一个点与第二个点不连。
若a[1]<0 ,则至少需要 -a[i]的操作数,所以强行将a[1]改为0,并答案强制加-a[1]。
最后找出1到n+1的最短路

/*图论*/
#include<cstdio>
#include<queue> 
#include<vector>
#include<iostream>
using namespace std;
const int need=110003;
const int inf=1e9; 

int n,nn;
//.....................................
inline void in_(int &d)
{
    char t=getchar();bool mark=false;
    while(t<'0'||t>'9') {if(t=='-') mark=true;t=getchar();}
    for(d=0;!(t<'0'||t>'9');t=getchar()) d=(d<<1)+(d<<3)+t-'0';
    if(mark) d=-d;
}
//.....................................
struct bian
{
    int la,en,len;
};
vector<bian> w;
#define pb(a,b,c) push_back((bian){a,b,c})

int tot,fi[need];

void add(const int &a,const int &b,const int &c)
{
    tot++;
    w.pb(fi[a],b,c);
    fi[a]=tot;
} 
//.....................................
int s,e;

struct fy
{
    int dis,id;
    bool operator< (const fy &b) const
    {
        return dis>b.dis;
    }
};
priority_queue<fy> q;
#define pu(a,b) push((fy){a,b}) 

bool getans[need];
int dis[need];

void dijkstra(int s)
{
    for(int i=1;i<=nn;i++) dis[i]=inf,getans[i]=false;
    q.pu(0,s),dis[s]=0;
    int x,y,t;
    while(!q.empty())
    {
        while(!q.empty()&&getans[q.top().id]) q.pop();
        x=q.top().id;
        if(x==e) return ;
        getans[x]=true;
        q.pop();
        for(t=fi[x];t;t=w[t].la)
        {
            y=w[t].en;
            if(getans[y]) continue;
            if(dis[y]>dis[x]+w[t].len)
            {
                dis[y]=dis[x]+w[t].len;
                q.pu(dis[y],y);
            }
        }
    }
}
//.....................................

int main()
{
    w.resize(1);
    int n;scanf("%d",&n);
    int dans=0;
    int a;
    in_(a);
    if(a<0) dans=-a,add(1,2,0);
    else add(1,a+2,0);
    nn=max(nn,a+2);
    for(int i=2,a;i<=n;i++) 
    {
        in_(a);
        if(a>=0) add(i,i+a+1,0);
        nn=max(nn,i+a+1);
        add(i,i+1,1),add(i+1,i,1);
    }
    for(int i=n+1;i<=nn;i++) add(i,i+1,1),add(i+1,i,1);
    s=1,e=n+1;
    dijkstra(s);
    cout<<dis[e]+dans;
}

法二-1:堆优化动规

#include<cstdio>
#include<set>
#include<iostream>
#include<queue>
using namespace std;
const int need=100003;
const int inf=1e9;

int a[need];
int f[need];

struct fy
{
    int val,key;
    friend bool operator< (const fy a,const fy &b) 
    {
        //if(a.val==b.val) return a.key<b.key;
        return a.val>b.val;
    }
};

priority_queue<fy> q1;
#define pu(a,b) push((fy){a,b}) 
int q2=inf;
//............................................
inline void in_(int &d)
{
    char t=getchar();bool mark=false;
    while(t<'0'||t>'9') {if(t=='-') mark=true;t=getchar();}
    for(d=0;!(t<'0'||t>'9');t=getchar()) d=(d<<1)+(d<<3)+t-'0';
    if(mark) d=-d;
}
//............................................


int main()
{
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++) in_(a[i]);
    q1.pu(a[1]+1,a[1]+1);
    for(int i=1;i<=n;i++) 
    {
    //cout<<q1.top().key;
        while(!q1.empty()&&q1.top().key<=i) 
        {
            q2=min(q1.top().val-2*q1.top().key,q2);
            q1.pop();
            //cout<<q1.size();
        }
        f[i]=q2+i;
        if(!q1.empty()) f[i]=min(f[i],q1.top().val-i);
        q1.pu(f[i]+a[i+1]+i+1,a[i+1]+i+1);
    }
    cout<<f[n];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值