ACM训练日记—5月31日

      最近主要在看区间dp的题目和学习状压dp和一些零碎的数据结构(哈希,字典树,什么的),这次先整理一下区间dp的题目。

      Game of Sum 

题意:有n个数字排成一条直线,每个人每次可以从两端(左或右)中的任意一端取走一个或若干个数(获得价值为取走数之和), 但是他取走的方式一定要让他在游戏结束时价值尽量的高,请你算一下在游戏结束时,先取数的人价值与后取数人价值之差。

     dp[i][j]表示区间 i 到 j 取得最大值。dp[i][j]=min(dp[i][j],sum[i][j]-min(dp[i][k],dp[k+1][j]))。

int n,dp[105][105];
int sum[105];
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0) break;
        sum[0]=0;
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
            int x;
            scanf("%d",&x);
            sum[i]=sum[i-1]+x;
            dp[i][i]=x;
        }
        for(int len=1;len<n;len++)
        {
            for(int l=1;l+len<=n;l++)
            {
                int r=l+len;
                int t=0;
                for(int k=1;k<=len;k++)
                {
                    t=min(t,min(dp[l+k][r],dp[l][r-k]));
                }
                dp[l][r]=sum[r]-sum[l-1]-t;
            }
        }
        cout<<2*dp[1][n]-sum[n]<<endl;
    }

}

poj 1191 棋盘分割

      将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了(n-1)次后,连同最后剩下的矩形棋盘共有n块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行) 

来自:https://blog.csdn.net/chenzhenyu123456/article/details/49803485

       切横行x1 <= row < x2

dp[k][x1][y1][x2][y2]=min(dp[k-1][x1][y1][row][y2]+sum(row+1,y1,x2,y2),dp[k-1][row+1][y1][x2][y2]+sum(x1,y1,row,y2));

切纵行y1 <= cul < y2
dp[k][x1][y1][x2][y2] 

= min(dp[k-1][x1][y1][x2][cul]+sum(x1,cul+1,x2,y2), dp[k-1][x1][cul+1][x2][y2]+sum(x1,y1,x2,cul));

int s[9][9];  
int a[9][9];  
int ans[15][9][9][9][9];  
int getsum(int x1, int y1, int x2, int y2)  
{  
    int temp = s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1];  
    return temp*temp;  
}  
int dp(int k, int x1, int y1, int x2, int y2)  
{  
    if(ans[k][x1][y1][x2][y2] != -1)  
        return ans[k][x1][y1][x2][y2];  
    if(k == 0)  
        return getsum(x1, y1, x2, y2);  
    ans[k][x1][y1][x2][y2] = INF;  
    for(int row = x1; row < x2; row++)  
        ans[k][x1][y1][x2][y2] = min(ans[k][x1][y1][x2][y2], min(dp(k-1, x1, y1, row, y2)+getsum(row+1, y1, x2, y2), dp(k-1, row+1, y1, x2, y2)+getsum(x1, y1, row, y2)));  
    for(int cul = y1; cul < y2; cul++)  
        ans[k][x1][y1][x2][y2] = min(ans[k][x1][y1][x2][y2], min(dp(k-1, x1, y1, x2, cul)+getsum(x1, cul+1, x2, y2), dp(k-1, x1, cul+1, x2, y2)+getsum(x1, y1, x2, cul)));  
    return ans[k][x1][y1][x2][y2];  
}  
int main()  
{  
    int n;  
    while(Ri(n) != EOF)  
    {  
        int sum = 0; CLR(s, 0);  
        for(int i = 1; i <= 8; i++)  
            for(int j = 1; j <= 8; j++)  
                Ri(a[i][j]), sum += a[i][j];  
        for(int i = 1; i <= 8; i++)  
        {  
            for(int j = 1; j <= 8; j++)  
            {  
                if(i == 1 && j == 1)  
                    s[i][j] = a[i][j];  
                else if(i == 1)  
                    s[i][j] = s[i][j-1] + a[i][j];  
                else if(j == 1)  
                    s[i][j] = s[i-1][j] + a[i][j];  
                else  
                    s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];  
            }  
        }  
        CLR(ans, -1);  
        printf("%.3lf\n", sqrt((dp(n-1, 1, 1, 8, 8)*n - sum*sum)*1.0 / n / n));  
    }  
    return 0;  

}  

状压dp的知识点

1.’&’符号,x&y,会将两个十进制数在二进制下进行与运算,然后返回其十进制下的值。例如3(11)&2(10)=2(10)。


2.’|’符号,x|y,会将两个十进制数在二进制下进行或运算,然后返回其十进制下的值。例如3(11)|2(10)=3(11)。


3.’^’符号,x^y,会将两个十进制数在二进制下进行异或运算,然后返回其十进制下的值。例如3(11)^2(10)=1(01)。


4.’<<’符号,左移操作,x<<2,将x在二进制下的每一位向左移动两位,最右边用0填充,x<<2相当于让x乘以4。相应的,’>>’是右移操作,x>>1相当于给x/2,去掉x二进制下的最有一位。


这四种运算在状压dp中有着广泛的应用,常见的应用如下:


1.判断一个数字x二进制下第i位是不是等于1。


方法:if ( ( ( 1 << ( i - 1 ) ) & x ) > 0)


将1左移i-1位,相当于制造了一个只有第i位上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0,说明x第i位上是1,反之则是0。


2.将一个数字x二进制下第i位更改成1。


方法:x = x | ( 1<<(i-1) )


证明方法与1类似,此处不再重复证明。


3.把一个数字二进制下最靠右的第一个1去掉。


方法:x=x&(x-1)

数据不能太多(好像是<=16?),然后遍历所有状态的姿势就是用二进制来表示,01串,1表示使用,0表示未使用,就把所有的状态投射到很多二进制的数上(类似于hash?)然后对每个状态找上一"些"状态的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值