新生友谊赛

不敢出什么算法题,题目以一些基础的DP题为主,重递推的思路。

A题:算是7道里面的水题了,递推比较容易想。而且这道题其实是考察卡特兰数,我博客之前就写过这道题的题解和相关拓展。详细题解给链接,自己前往:http://www.cnblogs.com/kevinACMer/p/3724640.html

B题:期望DP,仍然是详细题解给链接,自己前往:http://www.cnblogs.com/kevinACMer/p/3724671.html

C题:简单DP,我打了一版记忆化搜索AC了,不过要注意就是数组只需要开50*50就行,开1000*1000估计就T了。题意:一根木棍长为L,然后要切N刀,这N刀的具体位置给出,每切一刀的代价为当前被切木棍的总长度,求总代价的最小值。想了下发现贪心不行,开始想DP。分析一下比较容易发现:你每切一刀,对后续产生的影响无非是被切的木棍一分为二,那么你状态转移也就是从这两小段里转移过来的。dp[i,j]=min{dp[i,k]+dp[k,j]}+j-i (k是i,j之间的木棍切割点),这个式子仔细想想应该能理解,当然这里的i,j和k指的都是具体的坐标长度,由于总长最大为1000,所以如果这样写你就必须要开到1000*1000的数组了。显然这里是可以优化的,单独用数组c[i]表示第i个位置的具体坐标,然后状态转移方程就变成dp[i,j]=min{dp[i,k]+dp[k,j]}+c[j]-c[i](k是i,j之间的木棍切割点编号),与之前类似但dp[][]却只要开50*50就可以了。思路就这样,可以记忆化搜索dfs打,也可以直接DP递推转移。代码如下:

 

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
int l,c[55],dp[55][55];
int dfs(int u,int v){
    if(dp[u][v]!=-1)return dp[u][v];
    int ans=dfs(u+1,v)+(c[v]-c[u]);
    for(int i=u+2;i<v;i++)ans=min(ans,dfs(u,i)+dfs(i,v)+c[v]-c[u]);
    dp[u][v]=ans;
    return ans;
}
int main()
{
    int n;
    while(scanf("%d",&l)!=EOF&&l!=0){
        scanf("%d",&n);
        c[0]=0;
        for(int i=1;i<=n;i++)scanf("%d",&c[i]);
        c[n+1]=l;
        memset(dp,-1,sizeof(dp));
        for(int i=0;i<=n;i++)
            dp[i][i+1]=0;
        printf("The minimum cutting is %d.\n",dfs(0,n+1));
    }
}

D题:这题比较有意思,主要是不好想到突破点,算是一道比较难的题了。题目短,题意好懂就不再复述。

首先应注意到这个条件:(a-1)2< b < a 2    这个条件转化一下就变成了0<a-√b<1。Sn=(a+√b)^n向上取整再模m,首先应该明确的是Sn是一个整数。那么我根据以上条件构造一个数列Fn=(a-√b)^n+(a+√b)^n再模m,显然Fn是一个整数(因为a-√b和a+√b互为共轭根,都N次方后,带根号的项会相互抵消,只剩下整数项)。又因为0<a-√b<1,所以0<(a-√b)^n<1,所以Sn=Fn=(a-√b)^n+(a+√b)^n再模m(仔细想想应该不难理解)。先不管模m的问题,我们找到了Sn的一个表达式,即:Sn=(a-√b)^n+(a+√b)^n。这个表达式如果数学感觉比较好应该马上反应过来其实这是一个二阶递推式对应的最后通项公式(类似大家熟悉的斐波那契数列,斐波那契数列的通项公式在求的时候是用特征根方程求解这个F(n+1)=F(n)+F(n-1)二阶递推式)。对于一切二阶递推式,当它的特征根方程有两个不同解时,该递推式对应的通项公式均为An=A*n^x1+B*n^x2(其中x1,x2为特征根方程的两个解,A,B是待定系数且一般由初始值A1,A2解得)。那么我们现在可以反向构造出Sn的原始二阶递推式,由韦达定理可以反向构造出:Sn=2*a*Sn-1+(b-a²)*Sn-2(n-1,n-2对应的都是下标)当然,Sn别忘了最后都是要模m的。然后看了下数据范围,直接暴力递推显然是不行的,那么对于这种二阶递推式,显然用矩阵快速幂取模优化一下就好。初始值:S0=2,S1=2*a.这里有个小坑就是在递推的时候由于b-a²是小于零的,在递推的时候就会出现负数了,那么再取模一下就会出问题了,所以在取模的时候记得先加m再对m取模就没问题了。代码如下:

#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
ll m;
struct martix{
    ll f[2][2];
};
martix mul(martix a,martix b){
    martix c;
    for(int i=0;i<2;i++){
        for(int j=0;j<2;j++){
            c.f[i][j]=0;
            for(int k=0;k<2;k++){
                c.f[i][j]+=a.f[i][k]*b.f[k][j]%m;
            }
        }
    }
    return c;
}
martix pow(martix a,ll b){
    martix c;
    c.f[0][0]=c.f[1][1]=1;
    c.f[0][1]=c.f[1][0]=0;
    while(b){
        if(b&1)
            c=mul(c,a);
        a=mul(a,a);
        b=b>>1;
    }
    return c;
}
int main()
{
    ll a,b,n;
    while(scanf("%I64d %I64d %I64d %I64d",&a,&b,&n,&m)!=EOF){
        martix c;
        c.f[0][0]=2*a;
        c.f[0][1]=1;
        c.f[1][0]=b-a*a;
        c.f[1][1]=0;
        c=pow(c,n-1);
        ll ans=((2*a*c.f[0][0]+2*c.f[1][0])%m+m)%m;
        printf("%I64d\n",ans);
    }
    return 0;
}

E题:本来这题准备放道防AK的难题,结果想想还是算了,换了道水题。看懂题意,开始凭感觉瞎猜就能差不多得出结论了,试着打一发然后就AC了。

就是找规律,发现结论就是:把m,m,m-1,m-1,m-2,m-2,m-3,m-3..........前n项求出来输出即可。

构造的时候那行最小的最大行应该排列如下:.....      m-3  m-2  m-1  m    |   m  m-1  m-2  m-3    . ....

至于怎么证明,这我就没仔细想了,据说网上有证明,我没认真去看具体证明,但还是给个别人具体证明的链接,感兴趣的可以去看看。http://blog.csdn.net/spark_007/article/details/9017533

一切为了AC,就凭感觉猜!代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
int main(){
  int sum;
  while(~scanf("%d%d",&m,&n)){
      sum=0;
      for(int i=m;i>=m-(n-1)/2;i--){
        sum+=i;
      }
    printf("%d\n",sum*2-(m-(n-1)/2));
  }
}

F题:概率DP,看上去跟省赛那道还有点像,其实还是有本质区别的。详细题解给链接,自己前往:http://www.cnblogs.com/kevinACMer/p/3723531.html
G题:题意:数轴上有N个城市,给出N个城市的具体位置,选择其中的M个城市建造邮局,求所有城市到对应最近邮局的距离之和的最小值。

这是道比较经典的DP了,看数据范围那么小肯定也是DP了,N^3都能过了,预处理一下,我是N^2的预处理,但是N^3的预处理也是能AC的。定义dp[i][j]表示前i个城市里已经建造了j个邮局的最短距离,然后考虑状态转移,dp[i][j] = min(dp[i][j],dp[k][j-1]+sum[k+1][i])相当于枚举决策前i个城市中哪个城市建造第j个邮局,sum[i][j]表示在i到j范围内的城市中只建造一个邮局的距离之和(这个就是需要预处理的部分),dp[maxN][maxM]是我们要求的。sum[i][j]传统的预处理是暴力N^3的,其实是可以优化的。例如:求sum[1][4]的话邮局需要建立在2或者3处,放在2处的消耗为p4-p2+p3-p2+p2-p1=p4-p2+p3-p1(pi代表具体位置),放在3处的结果为p4-p3+p3-p2+p3-p1=p4+p3-p2-p1,可见,将邮局建在2处或3处是一样的。现在接着求sum[1][5],现在处于中点的村庄是3,那么1-4到3的距离和刚才已经求出了,即为sum[1][4],所以只需再加上5到3的距离即可。同样,求sum[1][6]的时候也可以用sum[1][5]加上6到中点的距离。

预处理的sum[i][j]其实是满足递推式:

sum[i][j]=sum[i][j-1]+(V[j]-V[posti])(其中posti=(i+j)/2)V[]数组是用来储存N个城市对应位置的。预处理完然后初始化dp的起始边界:dp[i][1] = sum[1][i],最后就开始递推dp就行了,暴力N^3的dp。代码如下:

#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 305
#define INF 0x3f3f3f3f
using namespace std;
int v,p,V[maxn],sum[maxn][maxn],dp[maxn][maxn];
void init()
{
    memset(sum,0,sizeof(sum));
    for(int i=1; i<=v; i++)
        for(int j=i+1; j<=v; j++)
        {
            int posti=(i+j)/2;
            sum[i][j]=sum[i][j-1]+(V[j]-V[posti]);
        }
}
int main()
{
    while(scanf("%d%d",&v,&p)!=EOF)
    {
        for(int i=1; i<=v; i++)scanf("%d",&V[i]);
        init();
        for(int i=1; i<=v; i++)
        {
            dp[i][1]=sum[1][i];
            for(int j=2; j<=p; j++)
                dp[i][j]=INF;
        }
        for(int i=1; i<=v; i++)
        {
            for(int j=2; j<=p; j++)
            {
                for(int k=1; k<i; k++)
                {
                    dp[i][j]=min(dp[i][j],dp[k][j-1]+sum[k+1][i]);
                }
            }
        }
        printf("%d\n",dp[v][p]);
    }
    return 0;
}


整体来说,这些题目都不难,比赛也只有3小时,不管怎样,赛后把题都弄懂吧,好好总结,一起加油~

 

 

 

posted @ 2014-05-30 21:53  Kevin_CongTang  阅读(185)  评论(0编辑  收藏  举报