2018/7/6-纪中某C组题【jzoj1192,jzoj1397,jzoj1736】

421 篇文章 4 订阅
88 篇文章 0 订阅

前言

全体爆零,十分开心


正题


T1:矩阵

大意

就是N个矩阵,然后进行矩阵乘法( nm n ∗ m mp m ∗ p 的矩阵相乘就会变成 np n ∗ p 的矩阵并且运算次数是 nmp n ∗ m ∗ p ),然后求最小乘法运算次数。

考试时

一直以为会是图论,然后想来想去(什么网络流,SPFA啊),然后就是不会做。

解题思路

首先讲解一下矩阵乘法的性质:
假设有两个矩阵A和B
ABBA A ∗ B ≠ B ∗ A
由此因为题目说矩阵一点可以进行相乘,所以前一个矩阵的宽一定等于后一个矩阵的长,然后就可以进行区间dp:
f[i][j] f [ i ] [ j ] 表示将 ij i ∼ j 合并为一个矩阵的最少运算次数
然后枚举中间分割线 mid m i d 表示 imid i ∼ m i d mid+1j m i d + 1 ∼ j 分开合并,然后所有的取最小值。

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int n,a[501],b[501],f[501][501];
int main()
{
    memset(f,127/3,sizeof(f));
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%d%d",&a[i],&b[i]);
        f[i][i]=0;//初始化
    }
    for (int j=2;j<=n;j++)
    {
        for (int i=j-1;i>=1;i--)
        {
            for (int mid=i;mid<j;mid++)
              f[i][j]=min(f[i][j],f[i][mid]+f[mid+1][j]+a[i]*b[mid]*b[j]);
              //动态转移
        }
    }
    printf("%d",f[1][n]);//输出
}

T2:圆盘取数

大意

有一个圆盘,上面有n个数,指针开始时指向第一个数,之后可以取走离指针距离不超过k里面的数,取走数代价是数的值,然后指针移动一格需要消耗代价为当前剩下最大的数。求取走所有数的最小代价。

考试时

开始时题目理解错了,然后后来改正后打了个贪心,然后WA

解题思路

首先我们可以发现取数字无论何时取走都需要固定的代价,代价就是所有数的和,所以我们只需要求指针转动的代价就好了。

首先我们先在不移动指针的情况下取走数字
这里写图片描述
然后将剩余部分展开
这里写图片描述

然后这就是一条链了,然后我们可以顺路收掉所有的所以每次剩下的一点只会是一段区间。
因为现在指针离最左边和最右边刚好都是k,移动一格也只能多取一个数,所有我们就可以把k的问题去掉了。
之后我们就可以进行区间dp
f[i][j][0/1] f [ i ] [ j ] [ 0 / 1 ] 表示只剩下 ij i ∼ j 的区间时指针在左/右边距离k。
然后我们现在重新定义这条链为 1n 1 ∼ n n n 为这条链的长度,然后我们要一个一个的去掉,所以我们是枚举一个w(n11)表示目前的长度然后枚举i表示开头。
之后我们要求 f[i][j][0/1] f [ i ] [ j ] [ 0 / 1 ] 了,我们有三种方式转移到 f[i][j] f [ i ] [ j ] ,我们拿在左边来计算,一是原本指针就在那个方向的左边,那么这样移动一格就好了

f[i1][j][0]+max[i1][j] f [ i − 1 ] [ j ] [ 0 ] + m a x [ i − 1 ] [ j ]

然后另一种是左一格的情况转一圈过来取
f[i1][j][1]+(nw)max[i1][j] f [ i − 1 ] [ j ] [ 1 ] + ( n − w ) ∗ m a x [ i − 1 ] [ j ]

最后一种是右一格的情况转一圈过来取
f[i][j+1][1]+(nw+1)max[i][j+1] f [ i ] [ j + 1 ] [ 1 ] + ( n − w + 1 ) ∗ m a x [ i ] [ j + 1 ]

动态转移方程
f[i][j][0]=minf[i1][j][0]+max[i1][j],f[i1][j][1]+(nk)max[i1][j],f[i][j+1][1]+(nk+1)max[i][j+1] f [ i ] [ j ] [ 0 ] = m i n f [ i − 1 ] [ j ] [ 0 ] + m a x [ i − 1 ] [ j ] , f [ i − 1 ] [ j ] [ 1 ] + ( n − k ) ∗ m a x [ i − 1 ] [ j ] , f [ i ] [ j + 1 ] [ 1 ] + ( n − k + 1 ) ∗ m a x [ i ] [ j + 1 ]

然后求右边的反过来就好了


代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#define mins(x,y,z) min(x,min(y,z))
using namespace std;
int n,k,t,answ,ans,a[2010],f[2010][2010][2],m[2010][2010];
int main()
{
    freopen("data10.in","r",stdin);
    scanf("%d%d",&n,&k);
    n=n-2*k-1;
    for (int i=-k;i<=n+k;i++)
    {
      scanf("%d",&t);
      answ+=t;//计算取数代价
      if (i>=1&&i<=n) a[i]=t;//去掉开始可以取的
    }
    for (int i=1;i<=n;i++)
    {
        int maxs=0;
        for (int j=i;j<=n;j++)
          m[i][j]=(maxs=max(maxs,a[j]));//预处理区间最大
    }
    ans=2147483647/3;
    for (k=n-1;k>=1;k--)
    {
        f[0][k][0]=f[0][k][1]=700000000;
        f[n-k+1][n+1][0]=f[n-k+1][n+1][1]=700000000;//防止取到界外
        for (int i=1;i<=n-k+1;i++)
        {
            int j=i+k-1;
            f[i][j][0]=mins(f[i-1][j][0]+m[i-1][j],f[i-1][j][1]+(n-k)*m[i-1][j],f[i][j+1][1]+(n-k+1)*m[i][j+1]);
            //动态转移
            f[i][j][1]=mins(f[i][j+1][1]+m[i][j+1],f[i][j+1][0]+(n-k)*m[i][j+1],f[i-1][j][0]+(n-k+1)*m[i-1][j]);
            //动态转移
        }
    }
    for (int i=1;i<=n;i++)
      ans=min(ans,min(f[i][i][0],f[i][i][1])+a[i]+answ);
      //求最小代价
    printf("%d",ans);
}

T3:扑克游戏

大意

一颗无穷大的完全二叉树
这里写图片描述
你有n张数值不同的牌,如果你将一张牌放到了一个节点那么代价就是 num(dep1) n u m − ( d e p − 1 ) 并且封锁子树,然后要求所有的牌放在树上的最小代价。

考试时

敲了一个贪心每次尽量平均分成两半子树传,然后只剩一个数时返回信息,最后进行统计

贪心代码
#include<cstdio>
#include<algorithm>
using namespace std;
int tot,n,a[10001],s,v[10001];
int tx(int x,int num,int sum,int dep)
{
    if (x<=1&&dep!=0) return sum*dep;
    int s=0,k=0;
    ++tot;
    for (int i=1;i<=n;i++)
      if (v[i]==num)
      {
        if (s*(dep+1)+a[i]*(dep+1)<=sum*(dep+1)/2) s+=a[i],v[i]=tot,k++;
        else v[i]=tot+1;
      }
    tot++;
    int w=tot;
    return tx(k,w-1,s,dep+1)+tx(x-k,w,sum-s,dep+1);
}
int main()
{   
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
      scanf("%d",&a[i]),s+=a[i];
    sort(a+1,a+1+n);
    printf("%d",tx(n,0,s,0));       
}

然后全WA

解题思路

就是合并果子(复制粘贴)
原理:
合并果子时最后一堆需要消耗 sum+=a[i](i=1n) s u m + = a [ i ] ( i = 1 ∼ n ) ,然后假设上一堆的需要果子子集S,那么代价就是 sum+=x(xS) s u m + = x ( x ⊆ S ) 。那么我们发现该数合并的次数其实就是这道题放在的深度加1。

正确原理是哈夫曼树(自己看我也不会)。

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int k,x,num,n1,n2,a1[30001],a2[30001],t[20001],w,sum;
int main()
{
    scanf("%d",&num);
    memset(a1,127/3,sizeof(a1));
    memset(a2,127/3,sizeof(a2));
    for (int i=1;i<=num;i++)
    {
        scanf("%d",&x);
        t[x]++;//桶
    }
    for (int i=1;i<=20000;i++)
    {
        while (t[i])//通排序
        {
            t[i]--;
            a1[++n1]=i;
        }
    }
    int i=1,j=1;
    k=1;
    while (k<num)
    {
        if (a1[i]<a2[j])//取最小值
        {
            w=a1[i];
            i++;
        }
        else
        {
            w=a2[j];
            j++;
        }
        if (a1[i]<a2[j])//取第二次
        {
            w+=a1[i];
            i++;
        }
        else
        {
            w+=a2[j];
            j++;
        }
        a2[++n2]=w;//加入第二个队列
        k++;//计算合并次数
        sum+=w;//计算价值
    }
    printf("%d",sum);
}

合并果子 O(n) O ( n ) 原理详见:
https://blog.csdn.net/mr_wuyongcong/article/details/80030964


后续

矩阵乘法白学了,合并果子白学了,区间dp白学了。这就是第一天的收获233。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值