【学术篇】一石三鸟的——洛谷2654——原核生物培养(石子合并果子)

传送门:https://www.luogu.org/problem/show?pid=2654

精简版题意:对n个数做k次操作,每次取最小的m个放在指定环形位置,进行m-1次合并,每次将相邻的两个数花费两数之和的代价将他们合并为两数之和,求最小代价。。

题目分析:用石子合并的方式合并果子。。(果子合并)

之间用纯模拟就好了。。(数据范围辣么小,怎么乱搞都能过啊。。)
至于合并果子?当时还是用来练手写堆的。。而现在我直接懒到直接priority_queue了。。
(反正省选 noi都开O2嘛2333)

此题做法:将所有数据压入堆中(堆排序,但有dalao用了基数排序虐场orz),根据贪心原则,每次从堆顶弹出两个最小的果子合并,再压入堆即可。

此题不偷懒的手写堆做法(年轻的我写的,连个快读都没有):

#include<cstdio>
int heap[10001],a[10001],size=0;
void swap(int &a,int &b)
{ int t=a; a=b; b=t; }
void push(int k)
{
    heap[++size]=k;
    int r=size;
    while(r>1&&heap[r]<heap[r>>1])
    {    swap(heap[r],heap[r>>1]); r>>=1;  }
}
int pop()
{
    int ans=heap[1],r=1,t;
    heap[1]=heap[size--];
    while(r<<1<=size)
    {
        t=r<<1;
        if(heap[t]>=heap[t+1]) t++;
        if(heap[r]<heap[t]) return ans;
        swap(heap[r],heap[t]);
        r=t;
    }
    return ans;
}
int main()
{
    int n,s=0; scanf("%d",&n);
    for(int i=1;i<=n;i++)
    { scanf("%d",&a[i]); push(a[i]);  }
    while(size>1)
    { int a=pop(),b=pop(); push(a+b); s+=a+b;    }
    printf("%d",s);
}

偷懒的priority_queue做法(加了快读都比上面短):

#include <cstdio>
#include <queue>
using namespace std;

#define gc getchar()

priority_queue<int,vector<int>,greater<int> > q; //小根堆 小根堆 小根堆

inline int gnum(){
    int a=0;char c=gc;bool f=0;
    for(;(c<'0'||c>'9')&&c!='-';c=gc);
    if(c=='0') c=gc,f=1;
    for(;c>='0'&&c<='9';c=gc) a=(a<<1)+(a<<3)+c-'0';
    if(f) return -a; return a;
}

int main(){
    int n=gnum(),ans=0;
    for(int i=1;i<=n;i++) q.push(gnum());
    while(q.size()>1){
        int u=q.top(); q.pop();
        int v=q.top(); q.pop();
        ans+=u+v; q.push(u+v);
    }
    printf("%d",ans);
}

至于石子合并?我刚开始是拒绝的。。其实是我发现自己不会写了 后来想了一会,好像有辣么一点印象。。然后循环的结果。。我是崩溃的。。里面的循环傻傻分不清了喂。。

于是我又去做了一遍。。

这题的原理:经典的环形dp
首先一看环形dp,先将数组扩到2n..
然后状态转移方程大概就是:

f[i][j]=min{f[i][k]+f[k][j]+sigma(a[i]..a[j])} ( k=[i..j) )

其中的sigma可以通过前缀和。。
然后枚举i,j,k就好了(我习惯枚举长度和i来推j..看个人喜好吧。。)

石子合并代码(这里要求最小值和最大值,应该是f最小值,g最大值)
然后就是我的a数组直接原地求前缀和了。。看上去有点方,但是能看。。

#include <cstdio>
#define gc getchar()

const int N=204;
const int INF=~0U>>2;
int f[N][N],g[N][N],a[N];

inline int gnum(){
    int a=0;char c=gc;bool f=0;
    for(;(c<'0'||c>'9')&&c!='-';c=gc);
    if(c=='-') c=gc,f=1;
    for(;c>='0'&&c<='9';c=gc) a=(a<<1)+(a<<3)+c-'0';
    if(f) return -a; return a;
}

int main(){
    int n=gnum();
    for(int i=1;i<=n;i++)
        a[i]=a[i-1]+gnum();
    for(int i=n+1;i<=n<<1;i++)
        a[i]=a[i-1]+a[i-n]-a[i-n-1];
    for(int l=2;l<=n;l++)
        for(int i=1;i+l-1<n<<1;i++){
            int j=i+l-1; f[i][j]=INF; g[i][j]=-INF;
            for(int k=i;k<j;k++){
                int a1=f[i][k]+f[k+1][j]+a[j]-a[i-1],
                    a2=g[i][k]+g[k+1][j]+a[j]-a[i-1];
                if(f[i][j]>a1) f[i][j]=a1;
                if(g[i][j]<a2) g[i][j]=a2;
            }
        }
    int minn=INF,maxn=-INF;
    for(int i=1;i<=n;i++){
        int j=i+n-1;
        if(f[i][j]<minn) minn=f[i][j];
        if(g[i][j]>maxn) maxn=g[i][j]; 
    } 
    printf("%d\n%d",minn,maxn);
}

好的。。扯了这么长时间= =
能看到这里的都是真爱吧(什么鬼)

上面的原理搞清楚了,
然后中间就用循环乱搞即可。。
(话说我第一遍还看错题了什么鬼)

本题的代码(其实是上面的合并,但是我重构啦♪(^∇^*)

#include <cstdio>
#include <queue>

using namespace std;
#define gc getchar()

priority_queue<int,vector<int>,greater<int> > q;

const int INF=~0U>>1;
int a[25],b[25],f[25][25],s[25];

inline int gnum(){
    int a=0;char c=gc; bool f=0;
    for(;(c<'0'||c>'9')&&c!='-';c=gc);
    if(c=='-') c=gc,f=1;
    for(;c>='0'&&c<='9';c=gc) a=(a<<1)+(a<<3)+c-'0';
    if(f) return -a; return a;
}

int main(){
    int n=gnum(),m=gnum(),k=gnum(),sum=0;
    for(int i=1;i<=n;i++)
        q.push(gnum());
    while(k--){
        int all=0;
        for(int i=1;i<=m;i++){
            int x=gnum();
            a[x]=q.top(); q.pop();
            all+=a[x];
        }q.push(all); 
        //有一件事你们不要和我学。。push进去的不要用dp出来的结果,而是要把质量的和push进去。。
        //刚刚又有一个小伙子过来吐槽。。。(记住这么干会全WA)。。
        for(int i=1;i<=m;i++)
            a[m+i]=a[i];
        for(int i=1;i<=m<<1;i++)
            s[i]=s[i-1]+a[i];
        for(int l=2;l<=m;l++)
            for(int i=1;i+l-1<m<<1;i++){
                int j=i+l-1; f[i][j]=INF;
                for(int k=i;k<j;k++){
                    int ans=f[i][k]+f[k+1][j]+s[j]-s[i-1];
                    if(f[i][j]>ans) f[i][j]=ans;
                }
            }
        int minn=INF;
        for(int i=1;i<=m;i++)
            if(f[i][i+m-1]<minn) minn=f[i][i+m-1];
        sum+=minn;
    }
    printf("%d",sum);
}

嗯 就是这样。。
完成了一次传说中的三合一。。
这三个题难度保证递增,然而会了前两个怎么都能拼出第三个嘛。。
然而我几乎忘了石子合并怎么做( ⊙o⊙ )~~

唉我还是太弱了

 

posted @ 2017-03-22 10:28  Enzymii  阅读(133)  评论(0编辑  收藏  举报