Pluto_Rabbit's Blog

蒟蒻妹纸一枚,轻拍

【学习】DP第三弹

Jacinth posted @ 2016年8月25日 13:24 in Lerning with tags DP bzoj , 1766 阅读

第三弹辣

屯的题快写完辣(✿◡‿◡)

开森~~O(∩_∩)O~~

第三弹开始辣

No. 状态
1019 Accepted
1996 Accepted
1864 Accepted
2660 Accepted
2423 Accepted
4321 Accepted
1087 Accepted
1090 Accepted
1801 Accepted
1899 Accepted
1237 Accepted

 

BZOJ-1019: [SHOI2008]汉诺塔

f[x][i],g[x][i] 分别表示当前x柱上有i个圆盘,将它们移至任意另一个柱的操作数,和另一柱子的编号。

f[x][i],g[x][i] 可由 f[][i-1],g[][i-1] 推得:

我们令 y = g[x][i-1] , z = 6-x-y;

1)x上i-1个圆盘移至y上

2)将x上的第i个圆盘,移至z

此时 f[x][i] = f[x][i-1] + 1;

然后我们再次移动y上的i-1个,此时进行分类讨论

当 f[y][i-1] == z 移到z后,全过程结束。 此时 f[x][i] += f[y][i-1], g[x][i] = z;

当 f[y][i-1] == x 我们需要将i-1个圆盘移至x,再将z上的第i个圆盘,移至y。因为g[x][i-1] == y,所以x上i-1个圆盘已经移动到y,全过程结束。此时 f[x][i] += f[y][i-1] + f[x][i-1] + 1, g[x][i] = y;

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int g[5][50],n;
char ch[50];
bool vis[10]={0};
LL f[5][50];

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=6;++i)
	{
		scanf("%s",ch);
		int l = ch[0] - 'A' + 1, r = ch[1] - 'A' + 1;
		if (!vis[l]) vis[l] = 1, g[l][1] = r, f[l][1] = 1;
	}
	for (int i=2;i<=n;++i)
	{
		for (int x=1;x<=3;++x)
		{
			int y = g[x][i-1], z = 6-y-x;
			f[x][i] = f[x][i-1] + 1;
			if (z == g[y][i-1])
			{
				f[x][i] += f[y][i-1], g[x][i] = z;
			}
			else
			{
				f[x][i] += f[y][i-1] + f[x][i-1] + 1;
				g[x][i] = y;
			}
		}
	}
	printf("%lld\n",f[1][n]);
	return 0;
}

BZOJ-1996: [Hnoi2010]chorus 合唱队

f[i][j][0/1]表示取到数列中从i到j的区间,上一个取到的是数列左边/右边的数

f[i][j][0]=f[i+1][j][0]*(a[i]<a[i+1])+f[i+1][j][1]*(a[i]<a[j])

f[i][j][1]=f[i][j+1][0]+(a[j]>a[i])+f[i][j+1][1]*(a[j]>a[j-1])

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;

const int mod = 19650827,NN = 1005;
int a[NN],f[NN][NN][2],n;

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;++i) scanf("%d",&a[i]);
	for (int i=1;i<=n;++i) f[i][i][0] = 1;
	for (int i=n;i>=1;--i)
		for (int j=i+1;j<=n;++j)
		{
			if (a[i] < a[i+1]) f[i][j][0] += f[i+1][j][0];
			if (a[i] < a[j]) f[i][j][0] += f[i+1][j][1];
			if (a[j] > a[i]) f[i][j][1] += f[i][j-1][0];
			if (a[j] > a[j-1]) f[i][j][1] += f[i][j-1][1];
			f[i][j][0] %= mod, f[i][j][1] %= mod;
		}
	printf("%d\n",(f[1][n][0] + f[1][n][1]) % mod);
	return 0;
}

BZOJ-1864: [Zjoi2006]三色二叉树

树形DP

先一遍DFS把树造粗来

0/1/2分别表示不同的颜色,f存最大值,g存最小值

DP一下就粗来辣~\(≧▽≦)/~

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
const int NN = 500050;
char ch[NN];
int root,f[NN][3],g[NN][3],tr[NN][2],cnt = 0;

void DFS(int &xx)
{
	xx = ++cnt;
	int tmp = ch[cnt]-'0';
	if (tmp) DFS(tr[xx][0]);
	if (tmp > 1) DFS(tr[xx][1]);
}

void DP(int xx)
{
	if (!xx) return;
	int l = tr[xx][0],r = tr[xx][1];
	DP(l), DP(r);
	for (int i=0;i<3;++i)
	{
		int x = (i+1) % 3, y =(x+1) % 3, z = (i) ? 0:1;
		f[xx][i] = max(f[l][x] + f[r][y], f[l][y] + f[r][x]) + z;
		g[xx][i] = min(g[l][x] + g[r][y], g[l][y] + g[r][x]) + z;
	}
}

int main()
{
	scanf("%s",ch+1);
	DFS(root);
	DP(1);
	printf("%d %d\n",max(f[1][0],max(f[1][1],f[1][2])),min(g[1][0],min(g[1][1],g[1][2])));
	return 0;
}

BZOJ-2660: [Beijing wc2012]最多的方案

首先可以发现斐波那契数非常少,然后我们可以预处理出这些斐波那契数,然后先贪心的去选用尽可能大的斐波那契数去构成这个数。

OIER都知道每个斐波那契数都可以用它之前的两个数替换。

dp[i][1/0]表示dp到第i个贪心选出来的斐波那契数,并且选/不选这个数的方案。

sum[i]表示一个前缀和。sum[j]-sum[i-1]表示第i个数到第j个数之间空出来多少个斐波那契数。

于是我们可以得到:

$dp[i][1] = dp[i-1][0] + dp[i-1][1];$
$dp[i][0] = dp[i-1][1]\times(\frac{sum[i]-sum[i-1]-1}{2})+dp[i-1][0]\times(\frac{sum[i]-sum[i-1]}{2});$
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int cnt;
LL f[110],n,dp[110][2],sum[110];
int main()
{
	scanf("%lld",&n);
	f[1] = 1ll, f[2] = 2ll;
	for (cnt=2;f[cnt] < n;) ++cnt, f[cnt] = f[cnt-1] + f[cnt-2];
	for (int i=cnt;i;i--) if (n >= f[i]) n -= f[i], sum[++sum[0]] = i;
	sort(sum+1,sum+1+sum[0]);
	dp[1][1] = 1;
	dp[1][0] = (sum[1] - 1)/2ll;	
	for (int i=2;i<=sum[0];++i)
	{
		dp[i][1] = dp[i-1][0] + dp[i-1][1];
		dp[i][0] = dp[i-1][1] * ((sum[i]-sum[i-1]-1)/2ll) + dp[i-1][0] * ((sum[i]-sum[i-1])/2ll);
	}
	printf("%lld\n",dp[sum[0]][1] + dp[sum[0]][0]);
	return 0;
}

BZOJ-2423: [HAOI2010]最长公共子序列

第一问和正常的最长公共子序列一样滴

当 ch1[i] == ch2[j] 时 f[i][j] = f[i-1][j-1] + 1;

当 ch1[i] != ch2[j] 时 f[i][j] = max(f[i-1][j],f[i][j-1]);

考虑方案数

当 ch1[i] == ch2[j] 时
$g[i][j] = g[i-1][j-1] + g[i-1][j]\times(f[i][j] == f[i-1][j]) + g[i][j-1]\times(f[i][j] == f[i][j-1]);$
当 ch1[i] != ch2[j] 时
$g[t][j] = g[i-1][j]\times(f[i][j] == f[i-1][j]) + g[i][j-1]\times(f[i][j] == f[i][j-1]) - g[i-1][j-1]\times(f[i][j] == f[i-1][j-1]);$
#include <bits/stdc++.h>
using namespace std;

const int mod = 100000000, NN = 5050;
char ch1[NN],ch2[NN];
int f[2][NN],g[2][NN],l1,l2,t,la;


int main()
{
	scanf("%s",ch1+1);
	l1 = strlen(ch1+1) - 1;
	scanf("%s",ch2+1);
	l2 = strlen(ch2+1) - 1;
	for (int i=1;i<=l2;++i) g[1][i] = 1;
	t = 1, la = 0;
	g[t][0] = 1;
	for (int i=1;i<=l1;++i)
	{
		t ^= 1, la ^= 1;
		g[t][0] = 1;
		for (int j=1;j<=l2;++j)
		{
			if (ch1[i] == ch2[j])
			{
				f[t][j] = f[la][j-1] + 1;
				g[t][j] = g[la][j-1] % mod;
				if (f[t][j] == f[la][j]) g[t][j] = (g[t][j] + g[la][j]) % mod;
				if (f[t][j] == f[t][j-1]) g[t][j] = (g[t][j] + g[t][j-1]) % mod;
			}
			else
			{
				f[t][j] = max(f[la][j],f[t][j-1]);
				if (f[t][j] == f[la][j]) g[t][j] = (g[t][j] + g[la][j]) % mod;
				if (f[t][j] == f[t][j-1]) g[t][j] = (g[t][j] + g[t][j-1]) % mod;
				if (f[t][j] == f[la][j-1]) g[t][j] = (g[t][j] - g[la][j-1] + mod) % mod;
			}
		}
		memset(f[la],0,sizeof(f[la]));
		memset(g[la],0,sizeof(g[la]));
	}
	printf("%d\n%d\n",f[t][l2],g[t][l2]);
	return 0;
}

BZOJ-4321: queue2

状态好难设计,蒟蒻狗带辣

f[i][j][0/1] 表示1..i排完队,j对不满足条件的人,i和i-1是否靠在一起的方案数

转移就舒服多辣

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int NN = 1005,mod = 7777777;
int n;
LL f[NN][NN][2];

int main()
{
	scanf("%d",&n);
	f[1][0][0] = 1;
	for (int i=1;i<=n;++i)
		for (int j=0;j<i;++j)
		{
			f[i+1][j+1][1] = (f[i+1][j+1][1] + 2*f[i][j][0] % mod) % mod;
			f[i+1][j-1][0] = (f[i+1][j-1][0] + (LL)f[i][j][0] * j % mod) % mod;
			f[i+1][j][0] = (f[i+1][j][0] + (LL)f[i][j][0] * (i-j-1) % mod) % mod;
			f[i+1][j+1][1] = (f[i+1][j+1][1] + f[i][j][1]) % mod;
			f[i+1][j][1] = (f[i+1][j][1] + f[i][j][1]) % mod;
			f[i+1][j][0] = (f[i+1][j][0] + (LL)(i-j) * f[i][j][1] % mod) % mod;
			f[i+1][j-1][0] = (f[i+1][j-1][0] + (LL)(j-1)*f[i][j][1] % mod) % mod;
		}
	printf("%lld\n",f[n][0][0]);
	return 0;
}

BZOJ-1087: [SCOI2005]互不侵犯King

状压DP

f1[i]表示i状态是否合法(即一行内是否有相邻的王)

f2[i][j]表示i,j状态相互是否合法(即相邻两行间是否符合王的要求)

cnt[i]表示i状态下王的个数。

预处理出f1,f2,cnt。

dp[j][p][k]表示第j行,p个王,状态为k

由于n好小啊,所以就状压辣

转移还是很方便的

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
using namespace std;
typedef long long LL;
 
int m,n,all,cnt[513];
LL ans,dp[10][100][513];
bool f1[513],f2[513][513];
 
void Pre()
{
    int sum;
    for (int i=0;i<=all;++i)
        if ((i & (i>>1)) == 0)
        {
            sum = 0;
            for (int x=i;x;x>>=1) sum += (x & 1);
            cnt[i] = sum, f1[i] = 1;
        }
    for (int i=0;i<=all;++i)
    if (f1[i])
        for (int j=0;j<=all;++j)
        if (f1[j])
        {
            if (((i&j) == 0) && ((i&(j>>1)) == 0) && ((j&(i>>1)) == 0))
            f2[i][j] = 1;
        }
}
 
int main()
{
    scanf("%d%d",&n,&m);
    all = (1<<n) - 1;
    Pre();
    for (int i=0;i<=all;++i)
        if (f1[i]) dp[1][cnt[i]][i] = 1;
    for (int j=1;j<n;++j)
        for (int k=0;k<=all;++k)
        if (f1[k])
            for (int i=0;i<=all;++i)
                if (f1[i] && f2[k][i])
                    for (int p=cnt[k];p+cnt[i]<=m;++p)
                        dp[j+1][p+cnt[i]][i] += dp[j][p][k];
    ans = 0ll;
    for (int i=0;i<=all;++i) ans += dp[n][m][i];
    printf("%lld\n",ans);
    return 0;
}

BZOJ-1090: [SCOI2003]字符串折叠

f[l][r]为l~r的最短折叠长度

f[l][r] = min(r-l+1, f[l][i]+f[i+][r]) (l<=i<=r)

考虑 i+1~r 可以由 l~i 重复得到,那么此时的长度为 (f[l][i] + 2 + Get_ws((r-i) / (i-l+1)+1))

和原先的 f[l][r] 取 min 即可得到

全过程用记忆化搜索可以实现辣

#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cstdio>
using namespace std;

const int NN = 105;

int f[NN][NN];
char ch[NN];
bool mark[NN][NN]={0};

bool Jug(int l,int r,int L,int R)
{
	if ((r-l+1) % (R-L+1) != 0) return 0;
	for (int i=l;i<=r;++i)
		if (ch[i] != ch[(i-l) % (R-L+1) + L]) return 0;
	return 1;
}

int Get_ws(int xx)
{
	int tmp = 0;
	while (xx) xx/=10 , ++tmp;
	return tmp;
}

int DP(int l,int r)
{
	if (l == r) return 1;
	if (mark[l][r]) return f[l][r];
	mark[l][r] = 1;
	int tmp = (r-l+1);
	for (int i=l;i<r;++i)
	{
		tmp = min(tmp,DP(l,i)+DP(i+1,r));
		if (Jug(i+1,r,l,i)) tmp = min(tmp,DP(l,i) +2 +Get_ws((r-i) / (i-l+1)+1));
	}
	return f[l][r] = tmp;
}

int main()
{
	scanf("%s",ch);
	printf("%d\n",DP(0,strlen(ch)-1));
	return 0;
}

BZOJ-1801: [Ahoi2009]chess 中国象棋

f[i][j][k] 表示已经排了i排,有j列有一颗棋子,有k列有两颗棋子的方案数

考虑第i排放1/2颗棋子,由第i-1排转移

放一颗:使 j-1,k -> j,k (放在没有棋子的列上) / j+1,k-1 -> j,k(放在有一颗棋子的列上)

放两颗:使 j-2,k -> j,k (放在没有棋子的列上) / j+2,k-2 -> j,k (放在有一颗棋子的列上) / j-1+1,k-1 -> j,k (一颗放在有一颗棋子的列上,另一颗放在没有棋子的列上)

组合数乘一乘就好辣

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int NN = 105,mod = 9999973;
LL f[NN][NN][NN],n,m,ans;

int main()
{
	scanf("%lld%lld",&n,&m);
	if (m > n) swap(n,m);
	f[0][0][0] = 1;
	ans = 0ll;
	for (int i=1;i<=n;++i)
	{
		for (LL j=0;j<=m;++j)
			for (LL k=0;k<=m-j;++k)
			{
				//1
				f[i][j][k] = f[i-1][j][k];
				if (j>0) f[i][j][k] = (f[i][j][k] +f[i-1][j-1][k] *(m-j-k+1ll) % mod) %mod;
				if (j<m && k>0) f[i][j][k] = (f[i][j][k] +f[i-1][j+1][k-1] *(j+1ll) %mod) %mod;
				//2
				if (j>1) f[i][j][k] = (f[i][j][k] +f[i-1][j-2][k] *((m-j-k+2ll)*(m-j-k+1ll)/2ll) %mod) %mod;
				if (j<m-1 && k>1) f[i][j][k] = (f[i][j][k] +f[i-1][j+2][k-2] * ((j+2ll)*(j+1ll)/2ll) %mod) %mod;
				if (j>0 && k>0) f[i][j][k] = (f[i][j][k] +f[i-1][j][k-1] *j %mod *(m-j-k+1ll) %mod) %mod;
			}
	}
	for (int j=0;j<=m;++j)
		for (int k=0;k<=m-j;++k) ans = (ans + f[n][j][k]) % mod;
	printf("%lld\n",ans);
	return 0;
}

1899: [Zjoi2004]Lunch 午餐

先对所有人按照吃饭时间的长短进行排序,易得吃饭时间越长的应该越早打饭,以此进行DP;

f[i][j] 表示第i个人,一队点餐所花费的时间为j的状态下,最少的时间

考虑这个人加入: //sum为排队时间的前缀和

若加入一队,该队新的花费时间为 当前点餐时间j+此人点餐时间+此人用餐时间

若加入二队,该队新的花费时间为 此人及以前所有人的点餐时间sum[i]-当前点餐时间j+此人用餐时间

转移搞一搞就可以辣

#include <bits/stdc++.h>
using namespace std;

const int NN = 205;

int n,ans,sum[NN]={0},f[NN][NN*NN];
struct A
{
	int x,y;
}a[NN];

bool cmp(A xx,A yy)
{
	return xx.y > yy.y;
}

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;++i) scanf("%d%d",&a[i].x,&a[i].y);
	sort(a+1,a+1+n,cmp);
	for (int i=1;i<=n;++i) sum[i] = sum[i-1] + a[i].x;
	memset(f,0x7f,sizeof(f));
	f[0][0] = 0;
	for (int i=1;i<=n;++i)
		for (int j=0;j<=sum[i-1];++j)
		{
			f[i][j] = min(f[i][j],max(f[i-1][j],sum[i]-j+a[i].y));
			f[i][j+a[i].x] = min(f[i][j+a[i].x],max(f[i-1][j],j+a[i].y+a[i].x));
		}
	ans = 0x7f7f7f7f;
	for (int i=0;i<=sum[n];++i) ans = min(ans,f[n][i]);
	printf("%d\n",ans);
	return 0;
}

BZOJ-1237: [SCOI2008]配对

要是可以相同配对就是水题辣(╯‵□′)╯︵┻━┻

然而题目坑爹还是要做啊~~~~(>_<)~~~~

听说每个数和它配对的数位置只可能相差2。

然后我们就可以大力分类讨论不要虚辣。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <cstring>
using namespace std;
#define INF 21000000000000000ll
typedef long long LL;
const int NN = 100005;
LL a[NN],b[NN],f[NN];
int n;

LL Cal(LL xx,LL yy)
{
	if (xx == yy) return INF;
	else return abs(xx-yy);
}

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;++i) scanf("%lld%lld",&a[i],&b[i]);
	if (a[1] == b[1] && n == 1)
	{
		puts("-1");
		return 0;
	}
	sort(a+1,a+1+n);
	sort(b+1,b+1+n);
	f[1] = Cal(a[1],b[1]) ;
	f[2] = min(f[1]+Cal(a[2],b[2]),Cal(a[1],b[2])+Cal(a[2],b[1]));
	for (int i=3;i<=n;++i)
	{
		f[i] = f[i-1] + Cal(a[i],b[i]);
		f[i] = min(f[i],f[i-2]+Cal(a[i-1],b[i])+Cal(b[i-1],a[i]));
		f[i] = min(f[i],f[i-3]+Cal(a[i-2],b[i])+Cal(a[i-1],b[i-1])+Cal(a[i],b[i-2]));
		f[i] = min(f[i],f[i-3]+Cal(a[i-2],b[i-1])+Cal(a[i-1],b[i])+Cal(a[i],b[i-2]));
		f[i] = min(f[i],f[i-3]+Cal(a[i-2],b[i])+Cal(a[i-1],b[i-2])+Cal(a[i],b[i-1]));
	}
	printf("%lld\n",f[n]);
	return 0;
}
Avatar_small
Flandre Scarlet 说:
2016年8月28日 11:32

为什么1996在第二弹也A了一遍啊= =

Avatar_small
Jacinth 说:
2016年8月29日 21:23

@Flandre Scarlet: 唔。。那个应该是1966 QAQ。已经修复bug辣~~


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter