动态规划复习

发现一个整理dp基础方程整理得挺好的博客:http://www.cnblogs.com/keshuqi/p/7715167.html

发现一个总结多叉树树形背包常见建模方法的博客:http://blog.csdn.net/no1_terminator/article/details/77824790

最近除了模拟赛和往年noip题自我测试,就只能搞点弱项专题训练了。

都是洛谷上的题,每次从水题开始:

便宜的回文

区间dp,对于一个字母,增删其实效果是相同的,取代价最小的即可。

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=3000+10;
int n,m,val[maxn],dp[maxn][maxn];
char c,s[maxn];

int aa;char cc;
int read() {
    aa=0;cc=getchar();
    while(cc<'0'||cc>'9') cc=getchar();
    while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
    return aa;
}

int main() {
    m=read();n=read(); int x,y;
    scanf("%s",s+1);
    for(int i=1;i<=m;++i) {
        do c=getchar();while(c<'a'||c>'z');
        x=c-'a'+1; y=min(read(),read());
        val[x]=y;
    }
    memset(dp,0x3f3f3f3f,sizeof(dp));
    for(int i=1;i<=n;++i) dp[i][i]=0;
    for(int i=1;i<n;++i) if(s[i]==s[i+1]) dp[i][i+1]=0;
    for(int l=1;l<n;++l) {
        for(int i=1;i<=n-l;++i) {
            int j=i+l;
            if(s[i]==s[j]&&l>1) dp[i][j]=min(dp[i][j],dp[i+1][j-1]);
            dp[i][j]=min(dp[i][j],dp[i+1][j]+val[s[i]-'a'+1]);
            dp[i][j]=min(dp[i][j],dp[i][j-1]+val[s[j]-'a'+1]);
        }
    }
    printf("%d",dp[1][n]);
    return 0;
}

  

 道路游戏

这是一道其实只有普及+/提高-的题,因为三方可过。

三方的代码:

int get_sum(int r,int pos,int t) {
  pos=(pos-r-1+n*m)%n+1;
  return sum[pos][r+t]-sum[pos][r];
}

int main() {
  n=read();m=read();p=read();
  for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) tu[i][j]=read();
  for(int i=1;i<=n;++i) cost[i]=read();
  for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) sum[i][j]=sum[i][j-1]+tu[(i+j-2)%n+1][j];
  for(int i=1;i<=m;++i) dp[i]=-INF;
  for(int i=0;i<m;++i) for(int j=1;j<=n;++j)
    for(int k=1;k<=p&&k+i<=m;++k) dp[i+k]=max(dp[i+k],dp[i]+get_sum(i,j,k)-cost[j]);
  printf("%d",dp[m]);
  return 0;
}

肯定是平方的解决方式要好一点啊,此题用三方做有什么意义呢?但是我太菜不会平方啊。。。

奶牛浴场:

这道题似乎哪里见过。似乎是做过的?

一看那个n是可以平方的,l、w是不行的,那肯定是n平方的喽。

情况比较多,需要都考虑到,但是数据比较水我也没有办法。

借用洛谷上 I_AM_HelloWord 的题解(侵删):

先枚举极大子矩形的左边界,然后从左到右依次扫描每一个障碍点,并不断修改可行的上下边界,从而枚举出所有以这个定点为左边界的极大子矩形。考虑如图2中的三个点,现在我们要确定所有以1号点为左边界的极大矩形。先将1号点右边的点按横坐标排序。然后按从左到右的顺序依次扫描1号点右边的点,同时记录下当前的可行的上下边界。

开始时令当前的上下边界分别为整个矩形的上下边界。然后开始扫描。第一次遇到2号点,以2号点作为右边界,结合当前的上下边界,就得到一个极大子矩形(如图3)。

同时,由于所求矩形不能包含2号点,且2号点在1号点的下方,所以需要修改当前的下边界,即以2号点的纵坐标作为新的下边界。第二次遇到3号点,这时以3号点的横坐标作为右边界又可以得到一个满足性质1的矩形(如图4)。

类似的,需要相应地修改上边界。以此类推,如果这个点是在当前点(确定左边界的点)上方,则修改上边界;如果在下方,则修改下边界;如果处在同一行,则可中止搜索(因为后面的矩形面积都是0了)。由于已经在障碍点集合中增加了整个矩形右上角和右下角的两个点,所以不会遗漏右边界与整个矩形的右边重合的极大子矩形(如图5)。

需要注意的是,如果扫描到的点不在当前的上下边界内,那么就不需要对这个点进行处理。

这样做是否将所有的极大子矩形都枚举过了呢?

可以发现,这样做只考虑到了左边界覆盖一个点的矩形,因此我们还需要枚举左边界与整个矩形的左边界重合的情况。这还可以分为两类情况。一种是左边界与整个举行的左边界重合,而右边界覆盖了一个障碍点的情况,对于这种情况,可以用类似的方法从右到左扫描每一个点作为右边界的情况。

另一种是左右边界均与整个矩形的左右边界重合的情况,对于这类情况我们可以在预处理中完成:先将所有点按纵坐标排序,然后可以得到以相邻两个点的纵坐标为上下边界,左右边界与整个矩形的左右边界重合的矩形,显然这样的矩形也是极大子矩形,因此也需要被枚举到。

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=5000+10;
int l,w,n,ans;

int aa;char cc;
int read() {
	aa=0;cc=getchar();
	while(cc<'0'||cc>'9') cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	return aa;
}

struct Node{
	int x,y;
}node[maxn];

bool cmp(const Node& a,const Node& b) {
	return a.x < b.x;
}

bool cmp2(const Node& a,const Node& b) {
	return a.y < b.y;
}

int main() {
	l=read();w=read();n=read();
	for(int i=1;i<=n;++i) {
		node[i].x=read();
		node[i].y=read(); 
	}
	node[++n].x=0;node[n].y=0;
	node[++n].x=l;node[n].y=0;
	node[++n].x=0;node[n].y=w;
	node[++n].x=l;node[n].y=w;
	sort(node+1,node+n+1,cmp);
	int xx,yy;
	for(int i=1;i<=n;++i) {
		xx=0;yy=w; 
		if(i!=1) ans=max(ans,(node[i].x-node[i-1].x)*w);
		for(int j=i+1;j<=n;++j) {
			ans=max(ans,(node[j].x-node[i].x)*(yy-xx));
			if(node[j].y<=xx||node[j].y>=yy) continue;
			if(node[j].y<node[i].y) xx=node[j].y;
			else if(node[j].y>node[i].y) yy=node[j].y;
			else break;
		}
		xx=0;yy=w;
		for(int j=i-1;j;--j) {
			ans=max(ans,(node[i].x-node[j].x*(yy-xx)));
			if(node[j].y<=xx||node[j].y>=yy) continue;
			if(node[j].y<node[i].y) xx=node[j].y;
			else if(node[j].y>node[i].y) yy=node[j].y;
			else break;
		}
	}
	sort(node+1,node+n+1,cmp2);
	for(int i=1;i<n;++i) ans=max(ans,(node[i+1].y-node[i].y)*l);
	printf("%d",ans);
	return 0;
}

  

翻转棋:

为什么感觉都做过呢,套路啊。。。

我们知道,一个格子要么翻一次要么不翻,我们发现如果第一行的格子翻或不翻的状态定了,那么所有格子的状态就定了。

由于n、m只有15,我们就直接枚举第一行格子的状态然后直接算出后面格子的状态。

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=15+2,maxs=1<<15|2,INF=1e7;
int n,m,now,nowans,ans=INF,ff[maxn]; bool ok,ykk;
bool tu[maxn][maxn],num[maxn][maxn];

int aa;char cc;
int read() {
	aa=0;cc=getchar();
	while(cc<'0'||cc>'9') cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	return aa;
}

void get_num(int p) {
	nowans=0;
	for(int i=m;i;--i) {
		nowans+=(num[1][i]=(p&1));
		p>>=1;
	}
}

int get_now(int x,int y) {
	int rs=tu[x][y]^num[x][y];
	if(x>1) rs^=num[x-1][y];
	if(x<n) rs^=num[x+1][y];
	if(y>1) rs^=num[x][y-1];
	if(y<m) rs^=num[x][y+1];
	return rs;
}

void print_ans() {
	memset(ff,0,sizeof(ff));
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(num[i][j]) ff[i]|=(1<<j-1);
	ans=nowans;
}

int main() {
	n=read(); m=read();
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) tu[i][j]=read();
	for(int p=0;p<(1<<m);++p) {
		memset(num,0,sizeof(num)); get_num(p); ok=0;
		for(int i=2;i<=n&&nowans<ans;++i) for(int j=1;j<=m;++j) {
			now=get_now(i-1,j);
			if(now) nowans+=(num[i][j]=1);
		}
		for(int i=1;i<=m;++i) if(get_now(n,i)) {
			ok=1; break;
		}
		if(!ok&&nowans<ans) print_ans();
	}
	if(ans==INF) printf("IMPOSSIBLE
");
	else {
		for(int i=1;i<=n;++i) {
			for(int j=1;j<=m;++j) {
				printf("%d ",ff[i]&1);
				ff[i]>>=1;
			}
			printf("
");
		}
	}
	return 0;
}

  

多人背包

求前k优解的和。

刚开始看到这个题的时候觉得非常不可做。

然后就想了一会(反正脑子也不清醒也想不到什么)就看题解了,然后题解说归并排序,秒懂。

假如dp[i][j][t]表示目前取到第i件物品,消耗体积j,的第t优解。

那么它一定由dp[i-1][j]、dp[i-1][j-v[i]]转移过来,直接把这两个里面前k个最优的归并弄出来就可以了。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxv=5000+10,maxk=50+5,maxn=200+10,INF=1e8;
int k,v,n,f[maxv][maxk],g[maxv][maxk],p[maxn],w[maxn],ans;

int aa;char cc;
int read() {
	aa=0;cc=getchar();
	while(cc<'0'||cc>'9') cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	return aa;
}

int main() {
	k=read();v=read();n=read();
	for(int i=1;i<=n;++i) {
		p[i]=read();w[i]=read();
	}
	for(int i=0;i<=v;++i) for(int t=1;t<=k;++t) g[i][t]=-INF;
	g[0][1]=0;
	for(int i=1;i<=n;++i) {
		for(int j=0;j<=v;++j) {
			if(j<p[i]) for(int t=1;t<=k;++t) f[j][t]=g[j][t];
			else {
				int pos1=1,pos2=1;
				for(int t=1;t<=k;++t) {
					if(pos2>k||(pos1<=k&&g[j][pos1]>=g[j-p[i]][pos2]+w[i])) f[j][t]=g[j][pos1++];
					else f[j][t]=g[j-p[i]][pos2++]+w[i];
				}
			}
		}
		memcpy(g,f,sizeof(f));
		memset(f,0,sizeof(f));
	}
	for(int i=1;i<=k;++i) ans+=g[v][i];
	printf("%d",ans);
	return 0;
}

  

二叉苹果树

一道树dp裸题,但是一直没有过,弄了很久发现是一个地方z写成了y。

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=100+10,INF=1e6;
int n,m,dp[maxn][maxn],sum;

int aa;char cc;
int read() {
	aa=0;cc=getchar();
	while(cc<'0'||cc>'9') cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	return aa;
}

int fir[maxn],to[2*maxn],nxt[2*maxn],e=0,v[2*maxn];
void add(int x,int y,int z) {
	to[++e]=y;nxt[e]=fir[x];fir[x]=e;v[e]=z;
	to[++e]=x;nxt[e]=fir[y];fir[y]=e;v[e]=z;
}

int g[maxn],size[maxn],tot[maxn];
void dfs(int pos,int f) {
	int y,z; size[pos]=1;
	dp[pos][0]=0;
	for(y=fir[pos];y;y=nxt[y]) {
		if(f==(z=to[y])) continue;
		g[z]=v[y]; dfs(z,pos); 
		size[pos]+=size[z]; g[pos]+=g[z];
		for(int i=m;~i;--i) {
			for(int j=0;j<=min(i,size[z]-1);++j) dp[pos][i]=min(dp[pos][i],dp[z][j]+dp[pos][i-j]);
			if(i>=size[z]) dp[pos][i]=min(dp[pos][i],dp[pos][i-size[z]]+g[z]);
		}
	}
}

int main() {
	n=read();m=n-1-read();
	int x,y,z;
	memset(dp,0x3f3f3f3f,sizeof(dp));
	for(int i=1;i<n;++i) {
		x=read();y=read();z=read();
		sum+=z; add(x,y,z);
	}
	dfs(1,0);
	printf("%d",sum-dp[1][m]);
	return 0;
}

  

硬币的游戏A Coin Game

这道题让我想起了一道题,但是记不到那道题具体是什么了,只记得当时没有看懂题目,主要是没有理解两个人都足够聪明是什么意思。

感觉这道题这句话是一样的道理:两个玩家都希望拿到最多钱数的硬币。

希望是希望,不一定能成功,但是题目的意思就是两个人都是最优策略。

A希望取价值和最大,相当于他想让B取价值和最小,但是在A取后B一定会使用最优策略,所以A要使B最优策略最劣。

然后就只能倒着做了。dp[i][j]表示取第i个的人在这一次取j个硬币可以得到的最大价值,为了简化转移,我们dp表示一个前缀最大价值来搞:

dp[i][j]=max(dp[i][j-1],w[i]-dp[i+j][min(n-i-j+1,2*j)]);

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=2000+10;
int n,w[maxn],dp[maxn][maxn];

int aa;char cc;
int read() {
    aa=0;cc=getchar();
    while(cc<'0'||cc>'9') cc=getchar();
    while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
    return aa;
}

int main() {
    n=read();
    for(int i=1;i<=n;++i) w[i]=read();
    for(int i=n;i;--i) w[i]+=w[i+1];
    for(int i=n;i;--i) 
        for(int j=1;j<=n-i+1;++j) 
            dp[i][j]=max(dp[i][j-1],w[i]-dp[i+j][min(n-i-j+1,2*j)]);
    printf("%d",dp[1][2]);
    return 0;
}

牛的词汇The Cow Lexicon

水题一道,先把每一段可以匹配的最大值预处理出来,然后dp,注意细节问题。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=600+10;
int n,m,len[maxn],mch[maxn][maxn],dp[maxn],debug;
char s[maxn],c[maxn][30];

int main() {
	scanf("%d%d",&n,&m);
	scanf("%s",s+1); 
	for(int i=1;i<=n;++i) {
		scanf("%s",c[i]+1);
		len[i]=strlen(c[i]+1);
	}
	int x,y;
	for(int i=1;i<=m;++i) {
		for(int j=1;j<=n;++j) {
			if(len[j]>m-i+1) continue;
			x=i;y=1;
			while(y<=len[j]&&x<=m) {
				while(x<=m&&s[x]!=c[j][y]) x++;
				if(x<=m&&s[x]==c[j][y]) y++,x++;
			}
			if(y>len[j]) mch[i][x-1]=max(mch[i][x-1],len[j]);
		}
	}
	for(int l=1;l<m;++l) for(int i=1;i<=m-l;++i) {
		mch[i][i+l]=max(mch[i][i+l],mch[i][i+l-1]);
		mch[i][i+l]=max(mch[i][i+l],mch[i+1][i+l]);
	}
	for(int i=1;i<=m;++i) {
		dp[i]=max(dp[i],dp[i-1]);
		for(int j=0;j<i;++j) dp[i]=max(dp[i],dp[j]+mch[j+1][i]);
	}
	printf("%d",m-dp[m]);
	return 0;
}

  

化工厂装箱员

这个题很有趣啊,感觉题目描述简直有毒,看懂题了之后dp比较裸,不要管我恶意压缩代码。使手中保持10个成品让dp非常简单暴力。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=100+10,maxt=13,maxs=13*13*13;
int n,dp[maxn][maxs],sum[maxn][4],mi[5];
char cc;

void get_dp(int f,int i,int x,int y,int z) {
	int t=x+y*mi[1]+z*mi[2];
	dp[i][t]=min(dp[i][t],f+1);
}

int main() {
	scanf("%d",&n); int x,y,z,now;
	for(int i=1;i<=n;++i) {
		do cc=getchar();while(cc<'A'||cc>'C');
		x=cc-'A';
		for(int p=0;p<3;++p) sum[i][p]=sum[i-1][p];
		sum[i][x]++;
	}
	if(n<=10) {
		printf("%d",(sum[n][0]!=0)+(sum[n][1]!=0)+(sum[n][2]!=0));
		return 0;
	}
	mi[0]=1; for(int i=1;i<=3;++i) mi[i]=mi[i-1]*11;
	memset(dp,0x3f3f3f3f,sizeof(dp)); x=0;
	for(int i=0;i<3;++i) x+=mi[i]*sum[10][i];
	dp[10][x]=0;
	for(int i=10;i<=n;++i) for(int j=0;j<mi[3];++j) if(dp[i][j]<=n){
		x=j%mi[1]; y=j/mi[1]%mi[1]; z=j/mi[2];
		if(i==n) {
			now=(x!=0)+(y!=0)+(z!=0);
			dp[n][0]=min(dp[n][0],dp[n][j]+now);
			continue;
		}
		if(x) now=min(n,i+x),get_dp(dp[i][j],now,sum[now][0]-sum[i][0],y+sum[now][1]-sum[i][1],z+sum[now][2]-sum[i][2]);
		if(y) now=min(n,i+y),get_dp(dp[i][j],now,x+sum[now][0]-sum[i][0],sum[now][1]-sum[i][1],z+sum[now][2]-sum[i][2]);
		if(z) now=min(n,i+z),get_dp(dp[i][j],now,x+sum[now][0]-sum[i][0],y+sum[now][1]-sum[i][1],sum[now][2]-sum[i][2]);
	}
	printf("%d",dp[n][0]);
	return 0;
}

  

TA-Station

又是一个裸的树dp。不知道大家是怎么选难度等级的。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
const int maxn=1e6+10;
int n; ll ans,nowans=1;

int aa;char cc;
int read() {
	aa=0;cc=getchar();
	while(cc<'0'||cc>'9') cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	return aa;
}

int fir[maxn],to[2*maxn],nxt[2*maxn],e=0;
void add(int x,int y) {
	to[++e]=y;nxt[e]=fir[x];fir[x]=e;
	to[++e]=x;nxt[e]=fir[y];fir[y]=e;
}

ll dep[maxn],size[maxn],fa[maxn];
void dfs(int pos,int d) {
	dep[pos]=d; size[pos]=1; ans+=d; int y,z;
	for(y=fir[pos];y;y=nxt[y]) {
		if((z=to[y])==fa[pos]) continue;
		fa[z]=pos; dfs(z,d+1); size[pos]+=size[z];
	}
}

void dfs2(int pos,ll now,ll tot) {
	int y,z;
	if(now>ans||(now==ans&&nowans>pos)) ans=now,nowans=pos;
	for(y=fir[pos];y;y=nxt[y]) {
		if((z=to[y])==fa[pos]) continue;
		dfs2(z,now+tot+size[pos]-2*size[z],tot+size[pos]-size[z]);
	}
}

int main() {
	n=read(); int x,y;
	for(int i=1;i<n;++i) {
		x=read(); y=read();
		add(x,y);
	}
	dfs(1,1); dfs2(1,ans,0);
	printf("%lld",nowans);
	return 0;
}

  

打鼹鼠

还是不懂这道题哪里算得上提高+/省选-难度了。

首先我们不可能直接在n*n的网格图上跑,只能用鼹鼠来dp。

然后考了一个小优化就是当两个鼹鼠的time差>=2*n-2的时候无论怎样都是可以转移的。把鼹鼠按照time排序后就直接维护一个之前的鼹鼠与现在鼹鼠time差>=2*n-2的所有鼹鼠的dp最大值。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=1e4+10;
int l,n,dp[maxn],time[maxn],x[maxn],y[maxn],ans,now;

int aa;char cc;
int read() {
	aa=0;cc=getchar();
	while(cc<'0'||cc>'9') cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	return aa;
}

int main() {
	l=read(); n=read(); int pos=1;
	for(int i=1;i<=n;++i) {
		time[i]=read();
		x[i]=read(); y[i]=read();
		while(pos<i&&time[i]-time[pos]>=2*n-2) now=max(now,dp[pos++]); dp[i]=max(dp[i],now);
		for(int j=i-1;j>=pos;--j) if(abs(x[i]-x[j])+abs(y[i]-y[j])<=time[i]-time[j]) dp[i]=max(dp[i],dp[j]);
		dp[i]++;
		ans=max(ans,dp[i]);
	}
	printf("%d",ans);
	return 0;
}

  

百日旅行

又不小心开了一道水题,可以直接贪心水过。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
const int maxn=2e5+10;
const ll INF=1e17;
ll n,p,q,ans=INF;

ll trav(ll day,ll times) {
	ll x=day/times,y=day%times;
	return p*(times-y)*x*x+p*y*(x+1)*(x+1);
}

int main() {
	scanf("%lld%lld%lld",&n,&p,&q);
	for(int i=0;i<=n;++i) ans=min(ans,i*q+trav(n-i,i+1));
	printf("%lld",ans);
	return 0;
}

  

跳舞

我觉得我不能再刷水题了。。。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=5000+10,INF=1e8;
int n,T,s[maxn],b[maxn],dp[maxn][maxn],ans;

int aa;char cc;
int read() {
    aa=0;cc=getchar();
    while(cc<'0'||cc>'9') cc=getchar();
    while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
    return aa;
}

int main() {
    n=read(); T=read();
    for(int i=1;i<=n;++i) s[i]=read();
    for(int i=1;i<=n;++i) b[i]=read();
    for(int i=0;i<=n;++i) for(int j=0;j<T;++j) dp[i][j]=-INF;
    dp[0][0]=0; int x;
    for(int i=0;i<n;++i) for(int j=0;j<T;++j)
    if(dp[i][j]>-INF) {
        x=(j+1)%T;
        dp[i+1][j]=max(dp[i+1][j],dp[i][j]-s[i+1]);
        if(x) dp[i+1][x]=max(dp[i+1][x],dp[i][j]+s[i+1]);
        else dp[i+1][x]=max(dp[i+1][x],dp[i][j]+s[i+1]+b[i+1]);
    }
    for(int i=0;i<T;++i) ans=max(ans,dp[n][i]);
    printf("%d",ans);
    return 0;
}

  

豪华游轮

贪心,考虑把向前走的和向后走的分别合并,然后让中间角度尽可能接近180,剩下角度最后转。

忘记了什么是余弦定理,忘记了角度和弧度怎么转换,特别难过。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define db double
const int maxn=50,maxh=1e6+10;
const db pi=acos(-1);
int n,t;
db dis1,dis2,rnd[maxn],ans;
char s[22];
bool vis[400],g[400];

int aa;char cc;
int read() {
    aa=0;cc=getchar();
    while(cc<'0'||cc>'9') cc=getchar();
    while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
    return aa;
}

int main() {
    n=read();
    for(int i=1;i<=n;++i) {
        scanf("%s",s);
        if(s[0]=='r') rnd[++t]=read()%360;
        else if(s[0]=='l') rnd[++t]=(720-read())%360;
        else if(s[0]=='f') dis1+=read();
        else if(s[0]=='b') dis2+=read();
    }
    vis[0]=1;
    for(int i=1;i<=t;++i) {
    	memcpy(g,vis,sizeof(g));
    	for(int j=0;j<360;++j) if(g[j]) 
        vis[(int)(j+rnd[i])%360]=1;
	}
    for(int i=0;i<=180;++i) if(vis[180+i]||vis[180-i]) {
        ans=sqrt(dis1*dis1+dis2*dis2-2*dis1*dis2*cos((double)(180-i)*pi/180));
        break;
    }
    printf("%.6lf",ans);
    return 0;
}

  

前缀单词

我说我在后面开几道不是水题的题做,然后就看到这道通过三十多的题,我觉得应该是那种有点难度但是不至于太难的题,但是没想到还是水题,树dp水题,直接建字典树。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll unsigned long long
const int maxn=50+5,maxs=maxn*maxn;
int n,son[maxs][30],p[maxs],t;
char s[maxn];
ll dp[maxn];

void add_s(int pos) {
	int len=strlen(s+1),now=0,x;
	for(int i=1;i<=len;++i) {
		x=s[i]-'a';
		if(!son[now][x]) son[now][x]=++t;
		now=son[now][x];
	}
	p[now]=pos;
}

int fir[maxs],to[maxs],nxt[maxs],e=0;
void add(int x,int y) {
	to[++e]=y;nxt[e]=fir[x];fir[x]=e;
}

void dfs1(int pos,int now) {
	if(p[pos]) add(now,++t),now=t;
	for(int i=0;i<26;++i) if(son[pos][i]) dfs1(son[pos][i],now);
}

void dfs2(int pos) {
	dp[pos]=1; int y,z;
	for(y=fir[pos];y;y=nxt[y]) {
		dfs2(z=to[y]);
		dp[pos]*=dp[z];
	}
	dp[pos]++;
}

int main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%s",s+1),add_s(i);
	t=0;dfs1(0,0); dfs2(0);
	printf("%lld",dp[0]-1);
	return 0;
}

  

起床困难综合症

NOI还有水题的啊。。。。

直接算每一位如果是0最后会是什么,如果是1最后会是什么,然后直接数位dp就可以了。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=1e5+10,maxs=35;
int n,m,now[2][maxs],dp[maxs],ans;
char c[10];

int aa;char cc;
int read() {
	aa=0;cc=getchar();
	while(cc<'0'||cc>'9') cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	return aa;
}

void get_now(int &f,int p,int x) {
	if(p==1) f=f&x;
	else if(p==2) f=f|x;
	else f=f^x;
}

int main() {
	n=read(); m=read(); int x,y;
	for(int i=0;i<=32;++i) now[0][i]=0,now[1][i]=1;
	for(int i=1;i<=n;++i) {
		scanf("%s",c);y=read();
		x= c[0]=='A'? 1 : (c[0]=='O'? 2:3);
		for(int j=0;j<=32;++j) {
			get_now(now[0][j],x,y&1);
			get_now(now[1][j],x,y&1);
			y>>=1;
		}
	}
	for(int i=0;i<=32;++i) {
		dp[i]=dp[i-1];
		dp[i]|=max(now[0][i],now[1][i])<<i;
	}
	x=0;
	for(int i=32;~i;--i) {
		if((y=((m>>i)&1))) ans=max(ans,(x|((now[0][i])<<i))|dp[i-1]);
		x|=((now[y][i])<<i);
	}
	ans=max(ans,x);
	printf("%d",ans);
	return 0;
}

  

子串

不是很难,用f[i][j][t][r]表示当前我们A串匹配到了i位置,B串匹配到了j位置,并且一共取了t个子串,目前是否为最后取的子串的最后一位的状态。

第一维滚动,直接dp。注意细节问题。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
const int maxn=1000+10,maxm=200+10;
const ll mod=1e9+7;
int n,m,k;
char a[maxn],b[maxn];
ll f[2][maxm][maxm][2];

int aa;char cc;
int read() {
	aa=0;cc=getchar();
	while(cc<'0'||cc>'9') cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	return aa;
}

int main() {
	n=read();m=read();k=read();
	scanf("%s%s",a+1,b+1);
	int cur=0; f[0][0][1][1]=1;
	for(int i=1;i<=n;++i) {
		cur^=1;
		memset(f[cur],0,sizeof(f[cur]));
		for(int j=1;j<=m;++j) for(int t=1;t<=min(j,k);++t) {
			if(a[i]==b[j]) 
				(f[cur][j][t][1]+=f[cur^1][j-1][t][1]+f[cur^1][j-1][t-1][0]+f[cur^1][j-1][t-1][1])%=mod;
			(f[cur][j][t][0]+=f[cur^1][j][t][0]+f[cur^1][j][t][1])%=mod;
		}
		f[cur][0][1][1]=1;
	}
	printf("%lld",(f[cur][m][k][0]+f[cur][m][k][1])%mod);
	return 0;
}

  

这道题说简单吧,但是要写高精,说难吧,其实dp方程和转移也没多难。

用 $ dp[pos][i] $ 表示以 $ pos $ 为根的子树中, $ pos $ 所在连通块大小为i的最大答案(不乘i)。

然后$ dp[pos][0]= max ( dp[pos][i] imes i ) $ 。

kczno1题解中说复杂度看起来像三方的实际上是平方的:

每次转移是复杂度是 x之前的子树的sz*当前子树的sz

相当于之前子树所有点和当前子树的点组成的点对数

而每个点对只会在lca处被计算一次

所以复杂度O(n^2)

这样转移好处是可以没有除法。

我一开始没写高精交上去发现WA了一堆,我把long long 改成 unsigned long long 发现答案变了。

然后就检查转移方程检查了很久,最后弃疗去看题解,发现竟然和题解的思路一模一样,除了他写了高精。

不想打高精了,放个半成品在这。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll unsigned long long
const int maxn=700+10;
int n;

int aa;char cc;
int read() {
	aa=0;cc=getchar();
	while(cc<'0'||cc>'9') cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	return aa;
}

int fir[maxn],to[2*maxn],nxt[2*maxn],e=0;
void add(int x,int y) {
	to[++e]=y;nxt[e]=fir[x];fir[x]=e;
	to[++e]=x;nxt[e]=fir[y];fir[y]=e;
}

ll size[maxn],dp[maxn][maxn];
void dfs(int pos,int f) {
	int y,z;size[pos]=1;
	for(y=fir[pos];y;y=nxt[y]) {
		if((z=to[y])==f) continue;
		dfs(z,pos);size[pos]+=size[z];
	}
	dp[pos][1]=1; ll t;
	for(y=fir[pos];y;y=nxt[y]) {
		if((z=to[y])==f) continue;
		for(int i=size[pos];i;--i)  {
			t=dp[pos][i]*dp[z][0];
			for(int j=min(i-1,(int)size[z]);j;--j)
				dp[pos][i]=max(dp[pos][i],dp[pos][i-j]*dp[z][j]);
			dp[pos][i]=max(dp[pos][i],t);
		}
	}
	for(int i=1;i<=size[pos];++i) dp[pos][0]=max(dp[pos][0],dp[pos][i]*i);
}

int main() {
	n=read(); int x,y;
	for(int i=1;i<n;++i) {
		x=read(); y=read();
		add(x,y);
	}
	dfs(1,0);
	cout<<dp[1][0];
	return 0;
}

  

弱者就是会被欺负呀
原文地址:https://www.cnblogs.com/Serene-shixinyi/p/7755456.html