[WC 2015复习](五)动态规划

都是比较简单SB的东西,求各位去WC的神犇勿喷。

1、利用数据结构优化动态规划

(1)[BZOJ 1911][Apio 2010]特别行动队 (利用单调队列对DP进行斜率优化)

http://www.lydsy.com/JudgeOnline/problem.php?id=1911

[WC 2015复习](五)动态规划_第1张图片

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
 
#define MAXN 1000100
 
using namespace std;
 
typedef long long int LL;
 
LL n,f[MAXN],sum[MAXN]; //sum[]
LL q[MAXN],h,t,a,b,c;
 
double slope(int x,int y) //
{
    return (double)((f[x]+a*sum[x]*sum[x]-b*sum[x])-(f[y]+a*sum[y]*sum[y]-b*sum[y]))/(2*a*(sum[x]-sum[y]));
}
 
int main()
{
    scanf("%lld%lld%lld%lld",&n,&a,&b,&c);
    for(int i=1;i<=n;i++)
    {
        LL x;
        scanf("%lld",&x);
        sum[i]=sum[i-1]+x;
    }
    f[0]=0;
    h=t=1;
    q[1]=0;
    for(int i=1;i<=n;i++)
    {
        while(h<t&&slope(q[h+1],q[h])<=sum[i]) h++;
        f[i]=f[q[h]]+a*(sum[i]-sum[q[h]])*(sum[i]-sum[q[h]])+b*(sum[i]-sum[q[h]])+c;
        while(h<t&&slope(q[t],q[t-1])>slope(i,q[t])) t--;
        q[++t]=i;
    }
    printf("%lld\n",f[n]);
    return 0;
}

2、数位DP

(1)[BZOJ 1026][SCOI 2009]windy数

http://www.lydsy.com/JudgeOnline/problem.php?id=1026

首先DP预处理f[][]数组,f[i][j]表示长度为i的数字,最高位为j(可以为0)的windy数个数。

比较显然的思路就是求[a,b]之间的windy数个数,相当于求[1,b+1)中的windy数个数-[1,b+1)中的windy数个数。问题转化为求[1,x)中的windy数个数。我们可以通过取模得到x的每一位的数字以及x的长度cnt,例如x=13579的情况时,我们相当于求

1~9999中的windy数个数(长度小于cnt的windy数个数)+10xxx~13xxx形式的windy数个数+130xx~134xx形式的windy数个数+1350x~1356x的windy数个数+1357x形式的windy数个数。

注意若碰到不合法情况,例如求10xxx~12xxx形式的windy数个数,显然|1-0|<2,|1-2|<2,10xxx~12xxx的数字都不是windy数,就不用继续统计后面的数字了

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 15
#define MAXM 10

using namespace std;

typedef long long int LL;

int f[MAXN][MAXM]; //f[i][j]=最高位为i位,最高位数字为j的windy数个数
int digit[MAXN],cnt=0; //x有cnt位长,每一位的数字保存在digit数组中,数组中下标1的是个位

void init(int n) //预处理出数字x的每一位,保存在digit数组中
{
	if(n==0) return;
	digit[++cnt]=n%10;
	init(n/10);
}

void DP() //DP预处理
{
	for(int i=0;i<=9;i++) f[1][i]=1; //初始化
	for(int i=2;i<MAXN;i++)
		for(int j=0;j<=9;j++)
			for(int k=0;k<=9;k++)
				if(abs(j-k)>=2)
					f[i][j]+=f[i-1][k];
}

LL cal(int x) //求[1,x]中的windy数个数
{
	LL ans=0;
	cnt=0;
	memset(digit,0,sizeof(digit));
	init(x);
	//先求出位数不到cnt的windy数个数
	for(int i=1;i<cnt;i++)
		for(int j=1;j<=9;j++) //!!!!!!!!!!!
			ans+=f[i][j];
	for(int i=1;i<digit[cnt];i++) //再加上位数为cnt,最高位比x最高位小的windy数个数
		ans+=f[cnt][i];
	for(int i=cnt-1;i>0;i--) //枚举剩下的第i位
	{
		for(int j=0;j<digit[i];j++) //枚举第i位为数字j,j要比x的第i位小
			if(abs(digit[i+1]-j)>=2)
				ans+=f[i][j];
		if(abs(digit[i+1]-digit[i])<2) break; //x的第i位和第i+1位之差小于2,那么后面的不需要再枚举了
	}
	return ans;
}

int main()
{
	int a,b;
	cin>>a>>b;
	DP();
	cout<<cal(b+1)-cal(a)<<endl;
	return 0;
}

3、基环树DP

(1)[BZOJ 1040][ZJOI 2008]骑士

http://www.lydsy.com/JudgeOnline/problem.php?id=1040

此题就是没有上司的舞会的基环树林版本。我们可以先用一次DFS求出树林中每棵基环树上可以形成环的那条边E=U->V,或者说是去掉这条边就可以把基环树变成一般的树,然后我们删掉这条边,由于这条边删掉后,U和V中只能取一个或者都不取,因此要分两次进行树上DP,一次以U为起点树上DP,一次以V为起点树上DP。把两次f[U][0]和f[V][0]中取较大者计入答案即可。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
 
#define MAXN 1000100
 
using namespace std;
 
typedef long long int LL;
 
struct edge
{
    int u,v,next;
}edges[MAXN*2];
 
int head[MAXN],nCount=1;
 
void AddEdge(int U,int V)
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].next=head[U];
    head[U]=nCount;
}
 
bool visit[MAXN];
int val[MAXN];
LL f[MAXN][2],ans=0; //f[i][0]=不取点i得到的最大分数,f[i][0]=取点i得到的最大分数
int U,V,E; //去掉边E=(U->V)后,基环树变成了一般的树
 
void DFS(int u,int last) //找形成基环树的那条边E,last=链接u和父亲的边
{
    visit[u]=true;
    for(int p=head[u];p!=-1;p=edges[p].next)
    {
        int v=edges[p].v;
        if((p^1)==last) continue;
        if(visit[v]) //找到了形成基环树的那条边
        {
            U=u;
            V=v;
            E=p;
            continue;
        }
        DFS(v,p);
    }
}
 
void DP(int u,int last,int rolle) //last是连接u父亲和u的边,rolles是构成基环树的边
{
    f[u][1]=val[u]; //初始化
    f[u][0]=0;
    for(int p=head[u];p!=-1;p=edges[p].next)
    {
        int v=edges[p].v;
        if(p==rolle||(p^1)==rolle) //边p是之前标记的构成基环树的边,不能走
            continue;
        if((p^1)==last) continue;
        DP(v,p,rolle);
        f[u][1]+=f[v][0];
        f[u][0]+=max(f[v][0],f[v][1]);
    }
}
 
int main()
{
    memset(head,-1,sizeof(head));
    nCount=1;
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int v;
        scanf("%d%d",&val[i],&v);
        AddEdge(i,v);
        AddEdge(v,i);
    }
    for(int i=1;i<=n;i++) //枚举将每个点作为DFS的起点(每个基环树的根)
    {
        if(!visit[i])
        {
            DFS(i,0);
            DP(U,0,E);
            LL tmp=f[U][0];
            DP(V,0,E);
            tmp=max(tmp,f[V][0]);
            ans+=tmp;
        }
    }
    printf("%lld\n",ans);
    return 0;
}










你可能感兴趣的:([WC 2015复习](五)动态规划)