NOIP2006提高组题解

版权声明:欢迎转载+原文章地址~ https://blog.csdn.net/Hi_KER/article/details/81669435

T1:能量项链

考察知识:区间型动态规划

算法难度:XXX 实现难度:XX

分析:只要分析出状态转移方程就不难实现了

根据题意,我们可以把项链看作矩阵,项链的合并就是矩阵乘法

所以我们定义两个数组x[],y[],x[],y[]分别存矩阵的行列数

首先我们怎么把项链转化为矩阵呢:

    for(int i=1;i<=n;i++) scanf("%d",a+i);
    for(int i=1;i<=n;i++) x[i]=a[i],y[i]=a[i%n+1];
    for(int i=1;i<=n;i++) x[i+n]=x[i],y[i+n]=y[i];

第一行输入项链,第二行将项链转化为矩阵,但是项链是环状的,所以我们用它了两倍长度表示一个环

下面是动态规划一般套路:

定义状态方程:f(i,j)表示在区间[i,j]的矩阵的最大乘法次数

状态转移方程:f(i,j)=f(i,k)+f(k+1,j)+x[i]*y[k]*y[j] (i\leq k<j)

状态转移方程解释:我们先分别合并区间[i,k],[k+1,j],最大要做f(i,k)+f(k+1,j)次乘法,之后剩下合并后矩阵i,j要做x[i]*y[k]*y[j]次乘法

答案:ans=max(f(i,i+n-1))|(1\leq i\leq n)

代码:

#include<iostream>
#include<cstdio>
using namespace std;
int a[105],n;
int x[205],y[205],f[202][202];
int dp(int i,int j){
    if(j-i<1) return 0;
    if(f[i][j]) return f[i][j];
    for(int k=i;k<j;k++)
        f[i][j]=max(f[i][j],dp(i,k)+dp(k+1,j)+x[i]*y[k]*y[j]);
    return f[i][j];
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",a+i);
    for(int i=1;i<=n;i++) x[i]=a[i],y[i]=a[i%n+1];
    for(int i=1;i<=n;i++) x[i+n]=x[i],y[i+n]=y[i];
    int ans=0;
    for(int i=1;i<=n;i++) ans=max(ans,dp(i,i+n-1));
    printf("%d\n",ans);
    return 0;
}

T2:金明的预算方案

考察知识:子集型动态规划

算法难度:XX+ 实现难度:XXX

分析:和背包问题差不多,只不过这道题中物品可能有附件

对于有附件的物品,我们可以考虑是否要附件,或附件要那些

这道题动态规划的实现有两种填表方法:正着填和逆着填

正着填:

#include<cstdio>
#include<algorithm>
using namespace std;
struct goods{
    int v,p,q;//price,importance,kind
    int cnt,blg1,blg2;
    goods(){cnt=0;}
}A[65];
int n,mon;
int f[62][3500];
int main(){
    scanf("%d%d",&mon,&n);mon/=10;
    for(int i=1;i<=n;i++)
        scanf("%d%d%d",&A[i].v,&A[i].p,&A[i].q);
    for(int i=1;i<=n;i++){
        A[i].v/=10,A[i].p*=A[i].v;
        if(!A[i].q) continue;
        int pre=A[i].q;
        if(A[pre].cnt++) A[pre].blg2=i;
        else A[pre].blg1=i;
    }
    for(int i=1;i<=n;i++)
    for(int j=1;j<=mon;j++){
		f[i][j]=f[i-1][j];
        if(A[i].q) continue;
        int V=A[i].v,P=A[i].p;
        if(j>=V) f[i][j]=max(f[i][j],f[i-1][j-V]+P);
        else continue;
        if(A[i].cnt){
            int p1=A[i].blg1,p2=A[i].blg2;
            V+=A[p1].v,P+=A[p1].p;
            if(j>=V) f[i][j]=max(f[i][j],f[i-1][j-V]+P);
            if(A[i].cnt>1){
                V=V-A[p1].v+A[p2].v,P=P-A[p1].p+A[p2].p;
                if(j>=V) f[i][j]=max(f[i][j],f[i-1][j-V]+P);
                V+=A[p1].v,P+=A[p1].p;
                if(j>=V) f[i][j]=max(f[i][j],f[i-1][j-V]+P);
            }
        }
    }
    printf("%d\n",f[n][mon]*10);
    return 0;
}

反着填:

#include<cstdio>
#include<algorithm>
using namespace std;
struct goods{
	int v,p,q;//price,importance,kind
	int cnt,blg1,blg2;
	goods(){cnt=0;}
}A[65];
int n,mon;
int f[3500];
int main(){
	scanf("%d%d",&mon,&n);mon/=10;
	for(int i=1;i<=n;i++)
		scanf("%d%d%d",&A[i].v,&A[i].p,&A[i].q);
	for(int i=1;i<=n;i++){
		A[i].v/=10,A[i].p*=A[i].v;
		if(!A[i].q) continue;
		int pre=A[i].q;
		if(A[pre].cnt++) A[pre].blg2=i;
		else A[pre].blg1=i;
	}
	for(int i=1;i<=n;i++) if(!A[i].q)
	for(int j=mon;j>=1;j--){
		int V=A[i].v,P=A[i].p;
		if(j>=V) f[j]=max(f[j],f[j-V]+P);
		if(A[i].cnt){
			int p1=A[i].blg1,p2=A[i].blg2;
			V+=A[p1].v,P+=A[p1].p;
			if(j>=V) f[j]=max(f[j],f[j-V]+P);
			if(A[i].cnt>1){
				V=V-A[p1].v+A[p2].v,P=P-A[p1].p+A[p2].p;
				if(j>=V) f[j]=max(f[j],f[j-V]+P);
				V+=A[p1].v,P+=A[p1].p;
				if(j>=V) f[j]=max(f[j],f[j-V]+P);
			}
		}
	}
	printf("%d\n",f[mon]*10);
	return 0;
}

我们可以看到,对于这道题反着填不管是在代码长度,还是在占用空间上都优于正着填

T3:作业调度方案

考察知识:模拟

算法难度:XXX 实现难度:XXX

分析:如果你读懂题意了,这道题就很简单了

直接按照题目要求的操作就可以了(前提要看懂题目的意思,题目甚至已经把算法流程都告诉你了)

这道题的具体实现我们可以用布尔数组表示加工的情况,按照加工队列一个一个操作,修改数组就可以了

代码:

#include<cstdio>
#include<algorithm>
#define F(var,L,R) for(int var=L;var<=R;var++)
int n,m,que[405],Q[22][22],T[22][22],ans;
bool mac[42][802];
bool empty(int obj,int L,int R){//判断区间是否为空 
	F(i,L,R) if(mac[obj][i]) return false;
	return true;
}
int cover(int obj,int start,int len){//寻找需要覆盖的区间 
	F(i,start,800) if(empty(obj,i,i+len-1)){
		F(j,i,i+len-1) mac[obj][j]=true;//修改区间 
		return i+len-1;
	} return -1;
}
int main(){
	scanf("%d%d",&m,&n);
	F(i,1,n*m) scanf("%d",que+i);
	F(i,1,n) F(j,1,m) scanf("%d",&Q[i][j]);
	F(i,1,n) F(j,1,m) scanf("%d",&T[i][j]);
	F(i,1,n*m){
		int cnt=++Q[que[i]][0];//第cnt道工序 
		T[que[i]][cnt]=cover(Q[que[i]][cnt],T[que[i]][cnt-1]+1,T[que[i]][cnt]);
	}
	F(i,1,n) ans=std::max(ans,T[i][m]);
	printf("%d\n",ans);
	return 0;
}

T4:2^k进制数

考察知识:高精度,递推,数学

算法难度:XXX 实现难度:XXXX

分析:你可以把k<=3,w<=21时候对应的值做成表,然后你就会发现它就是杨辉三角形的一部分,你可以考虑用排列组合求方案数

但是我要讲另一种递推方法:

f(i,j)表示第1位为j且长度为i的2^k进制数的个数

递推方程:f(i,j)=\sum_{k=j+1}^{2^k-1}f(i-1,k)

边界:f(1,j)=1\,\,||\,\,0< j< 2^k

但是实现这个方程有很多细节,不如为了不MLE要有滚动数组,等等

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct bign{
    int a[205],len;
    bign(){a[0]=0,len=1;memset(a,0,sizeof(a));}
    void get_v(int v){
        bign(); if(!v) return; len=0;
        while(v) a[len++]=v%10,v/=10;
    }
    friend bign operator + (bign A,bign B){
        if(A.len<B.len) swap(A,B);
        for(int i=0;i<B.len;i++) A.a[i]+=B.a[i];
        A.a[A.len]=0;
        for(int i=0;i<A.len;i++) if(A.a[i]>9) A.a[i]-=10,A.a[i+1]++;
        if(A.a[A.len]) A.len++;
        return A;
    }
    void out(bool Entr=true){
        for(int i=len-1;i>=0;i--) putchar('0'+a[i]);
        if(Entr) putchar('\n');
    }
}f[2][512],f_[512],ans;
int k,w;
bool ALL; 
int main(){
    scanf("%d%d",&k,&w);
    int n=w/k+1,m=1<<k;
    if(n>(1<<k)-1) n=(1<<k)-1,ALL=true;
    for(int i=1;i<m;i++) f[1][i].get_v(1);
    for(int i=2;i<=n;i++){//递推 
        f[i%2][m-i].get_v(1);
        if(ALL||i<n) ans=ans+f[i%2][m-i];
        for(int j=m-i-1;j>0;j--){
            f[i%2][j]=f[i%2][j+1]+f[(i-1)%2][j+1];
            if(ALL||i<n) ans=ans+f[i%2][j];
        }
    }
    if(n!=1&&!ALL)//处理 
      for(int i=min((1<<(w%k))-1,m-n);i>0;i--)
        ans=ans+f[n%2][i];
    ans.out();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Hi_KER/article/details/81669435