【学术篇】一石三鸟的——洛谷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⊙ )~~
唉我还是太弱了