最近主要在看区间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 < y2dp[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?)然后对每个状态找上一"些"状态的