poj3373
类型是搜索,不过我是按照题解用DP来做的。
设m串长度为len.从后向前,设dp[i][j]为当前决策到第i位,当前的数对k取模余数是j的情况下,与m串后(len - i + 1)位的数字不同最小个数。
放一下渣程序:
#include<cstdio>
#include<cstring>
char s[200];
int divnum;
int mod[200][10] , dp[200][10001] , next_mod[200][10001] , now_num[200][10001];
int len;
void output(int ins , int mod)
{
if(ins == len) return;
printf("%d",now_num[ins][mod]);
output(ins + 1 , next_mod[ins][mod]);
}
int main()
{
register int i,j,k;
while(scanf("%s%d",s,&divnum) != EOF)
{
len = strlen(s);
for(i = 0 ; i <= 9 ; ++i) mod[len - 1][i] = i % divnum;
for(i = len - 2 ; i >= 0 ; --i)
{
for(j = 0 ; j <= 9 ; ++j)
{
mod[i][j] = mod[i+1][j] * 10 % divnum;
}
}
memset(dp , -1 , sizeof(dp));
memset(next_mod , 0 , sizeof(next_mod));
memset(now_num , 0 , sizeof(now_num));
dp[len][0] = 0;
for(i = len - 1 ; i >= 0 ; --i)
{
for(j = (i == 0 ? 1 : 0) ; j < 10 ; ++j)
{
for(k = 0 ; k < divnum ; ++k)
{
if(dp[i+1][k] == -1)
{
//dp[i][k] = -1;
continue;
}
int new_mod = (mod[i][j] + k) % divnum;
if(dp[i][new_mod] == -1 || dp[i][new_mod] > dp[i+1][k] + (j != (s[i] - '0')))
{
dp[i][new_mod] = dp[i+1][k] + (j != (s[i] - '0'));
next_mod[i][new_mod] = k;
now_num[i][new_mod] = j;
}
}
}
}
output(0,0);
puts("");
}
return 0;
}
poj3034
按照时间为阶段划分进行的水dp。不过有几点要特别注意。
你的程序能准确的算出一条线段上的整点吗?(想了一上午)
另外注意这个样例:
20 5 4 1 0 1 0 1 1 0 5 2 1 6 2 0 0 0
你猜他的答案是多少?
是4!
poj3254
状压dp.
这道题是属于一种计数问题。
一般步骤是由一层的某种状态向下一层拓展状态,把下一层每一个拓展出的状态都加上这一层这种状态的数目。
开始时,dp[0][0] = 1,然后从0-n层依次向下拓展,dp[n+1][0]就是答案。
代码:
#include<cstdio>
int map[15][15];
int dp[15][10000];
int n,m;
const int mod = 100000000;
void dfs(int row , int now_state , int next_state , int ins)
{
if(ins >= m)
{
dp[row + 1][next_state] = (dp[row + 1][next_state] + dp[row][now_state]) % mod;
return;
}
if(map[row + 1][ins] && (now_state & (1 << ins)) == 0)
{
dfs(row , now_state , next_state | (1 << ins) , ins + 2);
}
dfs(row , now_state , next_state , ins + 1);
}
int main()
{
register int i,j;
scanf("%d%d",&n,&m);
for(i = 1 ; i <= n ; ++i)
for(j = 0 ; j < m ; ++j)
scanf("%d",&map[i][j]);
dp[0][0] = 1;
for(i = 0 ; i <= n ; ++i)
for(j = 0 ; j < (1 << m) ; ++j)
{
if(dp[i][j])
{
dfs(i , j , 0 , 0);
}
}
printf("%d",dp[n + 1][0]);
return 0;
}
poj 2057
暂且跳过。。。
poj1947
树形DP.
依据贝神的教导,这类计数问题有两种思路:
1.根节点把子节点都甩掉,然后一点一点往上加;
2.根节点把子节点都算上,然后一点一点往下减。
dp[i][j]表示以i为根节点的子树保留j个节点(包括i自己)所需的最小切割边数。
所以初始化时把所有的dp[x][1]都清成0.(日前还不理解。。。)
能够有所启发的是。。。
对于每一个子节点,都进行选与不选两种决策。这是一种很好的思路。(其实就是背包)
poj3070
典型的矩阵乘法优化线性递推。
重新理解了一下矩阵乘法,而不是机械的背代码。
a*b(a行b列)的矩阵A和b*c(b行c列)的矩阵B相乘能够得到一个a*c的矩阵C。
其中矩阵A中第i行(共b个元素)由左到右与矩阵B中第j列(共b个元素)由上至下依次相乘的乘积和即为C矩阵第i行第j列的元素。
自己画个图模拟一下就很清楚了。
线性递推的时候就用这种方式设计转移矩阵。
一般n项的线性递推要用到一个n*n的转移矩阵。
光有矩阵乘法是没用的,需要快速幂才能降到log级。
别的不多说了。
代码:
#include<cstdio>
#include<cstring>
const int mod = 10000;
struct matrix
{
int m,n;
int d[2][2];
matrix()
{
m = n = 0;
memset(d , 0 , sizeof(d));
}
void operator *= (const matrix &b)
{
register int i,j,k;
matrix c;
c.m = m , c.n = b.n;
for(i = 0 ; i < m; ++i)
for(j = 0 ; j < b.n ; ++j)
for(k = 0 ; k < n ; ++k)
{
c.d[i][j] = (c.d[i][j] + d[i][k] * b.d[k][j]) % mod;
}
*this = c;
}
};
int main()
{
int s;
while(scanf("%d",&s) && s != -1)
{
matrix m0;
m0.m = 1 , m0.n = 2;
m0.d[0][0] = 0 , m0.d[0][1] = 1;
matrix add;
add.m = add.n = 2;
add.d[0][0] = 0 , add.d[0][1] = 1;
add.d[1][0] = 1 , add.d[1][1] = 1;
matrix ans;
ans.m = ans.n = 2;
ans.d[0][0] = 1 , ans.d[0][1] = 0;
ans.d[1][0] = 0 , ans.d[1][1] = 1;
while(s)
{
if(s & 1) ans *= add;
add *= add;
s >>= 1;
}
m0 *= ans;
printf("%d\n",m0.d[0][0]);
}
return 0;
}