• 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
    ",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
    ",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
    ",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();
    }
  • 相关阅读:
    SVG的引入历程
    Webstorm的常用快捷键
    TypeScript
    Vue Router
    ISO8601
    html5语义化
    删除已有的 HTML 元素
    with(){}方法
    Jquery学习笔记
    css权值问题
  • 原文地址:https://www.cnblogs.com/EdSheeran/p/8496929.html
Copyright © 2020-2023  润新知