18寒假最后一测+dijistra模板

题目名称

run

homework

cut

输入

run.in

homework.in

cut.in

输出

run.out

homework.out

cut.out

每个测试点时限

1秒

1秒

2秒

内存限制

64MB

64MB

64MB

测试点数目

10

10

10

每个测试点分值

10

10

10

是否有部分分

无

无

无

题目类型

传统

传统

传统

 

run

Background:

        穿过时空隧道,小明来到了秦朝。我是谁?我在哪?

            “哈哈哈,金科,发什么呆呢,你的刺杀已经失败了!”秦王躲在柱子后狂妄的笑。

            一股热血在小明体内流动,他竟情不自禁地追了上去。

Description:

        宫殿里一共有n根柱子,假设柱子在二维平面内的位置为(xi,yi)。金科现在在一号柱子,秦王现在在n号柱子。金科(小明)会飞檐走壁所以他从i号柱子到j号柱子的距离为min(|xi-xj|,|yi-yj|)。深知自己并不能刺杀秦王,所以小明知道事实上自己竭尽全力,也离秦王有一步之遥,所以小明想知道自己到达n号柱子的最短距离-1是多少,因为这将是他人生能走过的最后的路。

Input:

        第一行一个数n。

            接下来n-1行每行两个数x,y分别表示xi和yi。

Output:

            一行一个数表示从1号柱子走到n号柱子的最短距离减去1的长度。

Sample input:

5

2 2

1 1

4 5

7 1

6 7

Sample output:

1

Hint:

1->2->4->5最短距离为2,答案为1。

对于30%的数据,n<=1000。

对于100%的数据,n<=200000,0<xi,yi<1e9

数据保证答案>0,不然很奇怪。。

 题解:我们发现每个点分别于横纵坐标相邻的点连边就可以解决问题了,建边后直接跑最短路就可以了。最后不要忘记答案-1.(可以自己构图验证正确性)

 用堆优化+dijistra的板子

#include <bits/stdc++.h>
#include <algorithm>
using namespace std;
#define maxn 200005
#define maxm 1000005
bool vis[maxn];
int L,n,tot,head[maxn],dis[maxn];
inline void read(int &x){
    x=0;int f=1;char s=getchar();
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
    x*=f;
}
struct Point{
    int x,y,id;
}p[maxn];
struct edge{
    int nxt,v,w;
}G[maxm];
struct rp{
    int id,key;
    rp(int a,int b){
        id=a, key=b;
    }
};
bool cmp2(Point a, Point b){
    return a.x < b.x;
}
bool cmp1(Point a, Point b){
    return a.y < b.y;
}
struct cmp{
    bool operator()(const rp& a, const rp& b){
        return a.key > b.key;
    }
};

void add(int u, int v, int w){
    G[++tot].nxt = head[u];
    G[tot].w = w;
    G[tot].v = v;
    head[u] = tot;
    G[++tot].nxt = head[v];
    G[tot].w = w;
    G[tot].v = u;
    head[v] = tot;
}
void dijistra(){
    memset(dis, 127, sizeof(dis));
    priority_queue <rp,vector<rp>,cmp> q;
    q.push(rp(1, 0));
    dis[1] = 0;
    
    while(!q.empty()){
        rp u = q.top();
        q.pop();
        if(vis[u.id])continue;
        vis[u.id] = 1;
        for(int j = head[u.id]; j; j = G[j].nxt){
            int v = G[j].v;
            if(dis[v] > dis[u.id] + G[j].w){
                dis[v] = dis[u.id] + G[j].w;
                q.push(rp(v, dis[v]));
            }
        }
    }
}
int main()
{    
    freopen("run.in","r",stdin);
    freopen("run.out","w",stdout);
    read(n);
    for(int i = 1; i <= n; i++)
        read(p[i].x), read(p[i].y), p[i].id = i;
    sort(p+1, p+1+n, cmp2);
    for(int i = 1; i < n; i++)
        add(p[i].id, p[i+1].id, p[i+1].x - p[i].x);
    sort(p+1, p+1+n, cmp1);
    for(int i = 1; i < n; i++)
        add(p[i].id, p[i+1].id, p[i+1].y - p[i].y);
    dijistra();
    printf("%d\n",dis[n]-1);
}

 

 

 

homework

Background:

        小哪吒在开学前一天补作业。。。

Description:

        明天就要开学了!所以小哪吒需要尽快写完作业。现在,他有n份作业需要写,因为小哪吒精通变化,他可以再变出两只手来,所以他可以同时写两份作业。小哪吒两双手写同一份作业的时间是不一样的。为了节约时间,他用火眼睛睛看出了两双手写同一份作业分别需要ai,bi的时间。同样的,为了节约时间,他不会更改做题的顺序,即他会从第一份作业一直做到第n份作业。他唯一不知道的是,他能写完作业的最短时间。你能帮他求出来吗?

Input:

        第一行一个数n表示作业的数目。

            接下来n行每行两个数ai,bi分别表示小哪吒两双手写这份作业的时间。

Output:

        一行一个数表示写作业的最短时间。

Sample input:

3
1 3
1 3
1 3

Sample output:

    3

Hint:

            对于样例,用第一双手做完所有作业。

            对于40%的数据,n<=20。

            对于100%的数据,n<=2000。ai,bi<=3000。

 题解:我们用dp[i][0/1][j]表示去做第i份作业用第0/1只手时,另外一双手还需要做j的时间才能做完作业的最小时间;

dp[i][j][k] = min( dp[i - 1][ j ][ k+a[i - 1][ j ] ] + a[i - 1][ j ],  dp[i - 1][ j^1 ][ a[i - 1][ j^1 ] ] , 但我们有可能在0手有空时不一定用0手而是等1手完了用1手,

而这样当 k+a[i - 1][ j ]  = 0我们刚好就去用了空闲的手,故我们反着推就可以了,因为dp[i][j][k] k=0 时有可能那只手已经休息了很久了;

注意dp[i]中第i份作业还没做,所以我们最后要加上剩下的时间

#include <bits/stdc++.h>

using namespace std;
#define maxn 2005
int dp[maxn][2][3005], a[maxn][2];
inline void read(int &x){
    x=0;int f=1;char s=getchar();
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
    x*=f;
}

int main(){
    ///freopen("homework.in","r",stdin);
    //freopen("homework.out","w",stdout);
    int n;
    read(n);
    for(int i = 1; i <= n; i++)
        read(a[i][0]), read(a[i][1]);
    memset(dp, 0x3f, sizeof(dp));
    dp[1][0][0] = dp[1][1][0] = 0;
    for(int i = 1; i <= n; i++)
    for(int j = 0; j < 2; j++)
    for(int k = 0; k <= 3000; k++){
        dp[i+1][j][max(0, k-a[i][j])] = min(dp[i+1][j][max(0, k-a[i][j])], dp[i][j][k]+a[i][j]);
        dp[i+1][j][max(0, a[i][j^1]-k)] = min(dp[i+1][j][max(0, a[i][j^1]-k)] , dp[i][j^1][k]+k);
    }
    int ans = 1e9;
    for(int j = 0; j < 2; j++)
    for(int k = 0; k <= 3000; k++)
        ans = min(ans, dp[n][j][k] + max(k, a[n][j]));
    printf("%d\n",ans);
}

 

cut

Background:

        伐木工Zxr非常懒惰了,所以他在伐木的时候只会找准木头被虫蛀过的腐朽的地方砍下去。

Description:

        一块有N-1处被虫蛀过的地方,假设被虫蛀过的地方长度不计。这n-1个虫蛀将木头分成了n块,题目将依次给出这n块木头的长度。懒惰的zxr最多只想砍m次,并且希望可以借此把这块木头分得尽量均匀,即希望使砍伐后连续的木块中最长的尽量短。这个时候聪明的你跳了出来“我不仅能算出这个最短长度,我还能算出方案数!”

Input:

            第一行两个数n,m。

            接下来一行n个数分别表示这N块木头的长度。

Output:

            一行两个数分别表示最短的最长长度和方案数。

Sampleinput:

            3 2

            1 1 10

Sampleoutput:

            10 2

Hint:

            两种砍的方法: (1)(1)(10)和(1 1)(10)。

            对于30%的数据,n<=10,

            对于100%的数据,n<=50000,

0<=m<=min(n-1,1000),1<=Li<=1000.

题解:

第一问是一个十分显然的二分,贪心Check(),很容易就能求出最小的最大长度 Len 。

第二问求方案总数,使用 DP 求解。

  使用前缀和,令 Sum[i] 为前i根木棍的长度和。

  令 f[i][j] 为前i根木棍中切 j 刀,并且满足最长长度不超过 Len的方案数,那么:

    状态转移方程: f[i][j] = Σ f[k][j-1]   ((1 <= k <= i-1) &&  (Sum[i] - Sum[k] <= Len))  

  这样的空间复杂度为 O(nm) ,时间复杂度为 O(n^2 m) 。显然都超出了限制。

  下面我们考虑 DP 的优化。

  1) 对于空间的优化。

    这个比较显然,由于当前的 f[][j] 只与 f[][j-1] 有关,所以可以用滚动数组来实现。

    f[i][Now] 代替了 f[i][j] , f[i][Now^1] 代替了 f[i][j-1] 。为了方便,我们把 f[][Now^1] 叫做 f[][Last] 。

    这样空间复杂度为 O(n) 。满足空间限制。

  2) 对于时间的优化。

    考虑优化状态转移的过程。

    对于 f[i][Now] ,其实是 f[mink][Last]...f[i-1][Last] 这一段 f[k][Last] 的和,mink 是满足 Sum[i] - Sum[k] <= Len 的最小的 k ,那么,对于从 1 到 n 枚举的i,相对应的 mink 也一定是非递减的(因为 Sum[i] 是递增的)。我们记录下 f[1][Last]...f[i-1][Last] 的和Sumf,mink 初始设为 1,每次对于i将 mink 向后推移,推移的同时将被舍弃的 p 对应的 f[p][Last] 从Sumf中减去。那么 f[i][Now] 就是Sumf的值。

    这样时间复杂度为 O(nm) 。满足时间限制。

#include <bits/stdc++.h>

using namespace std;
#define maxn 50005
#define mod 10007
int dp[maxn][2], a[maxn], sum[maxn], L, n, m, Ml;
inline void read(int &x){
    x=0;int f=1;char s=getchar();
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
    while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();}
    x*=f;
}

bool check(int mid){
    int ll = 0, i = 0, group = 1, flag = 0;
        while(i <= n){
            if(ll + a[++i] <= mid)ll += a[i];
            else if(a[i] > mid)return false;
            else {
                group++;
                if(group > m + 1)return false;
                ll = a[i];
            }
        }
    return true;
}
void bs(){
    int l = Ml, r = sum[n];
    while(l != r){
        
        int mid = (l + r) >> 1;
        if(check(mid))r = mid;
        else l = mid+1;
        
    }
    L = r;//取大的
    printf("%d ",L);
}
void Dp(){
    int sumf = 0, mink = 0, ans = 0;
    int k = 0;
    for(int j = 0; j <= m; j++){
        sumf = 0; mink = 1;//一定要记得更新,忘了更新这个地方,WA了好多次
        for(int i = 1; i <= n; i++){
            if(!j){
                if(sum[i] <= L)dp[i][k] = 1;
            }
            else {
                while(mink < i && sum[i] - sum[mink] > L){
                        sumf -= dp[mink][k^1];
                        sumf = (sumf + mod) % mod;
                        mink++;            
                    }    
                dp[i][k] = sumf;
            }           
            
            sumf = (sumf + dp[i][k^1]) % mod;
        }
        ans += dp[n][k];
        ans %= mod;
        k^=1;//滚动数组
    }
    
    printf("%d\n",ans);
}
int main(){
    freopen("cut.in","r",stdin);
    freopen("cut.out","w",stdout);
    read(n),read(m);
    for(int i = 1; i <= n; i++){
        read(a[i]);
        Ml = max(Ml, a[i]);
        sum[i] = sum[i-1] + a[i];
    }
    bs();
    Dp();
}

 

posted @ 2018-03-03 16:00  Ed_Sheeran  阅读(301)  评论(0)    收藏  举报