• 动态规划复习


    发现一个整理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;
    }
    

      

    弱者就是会被欺负呀
  • 相关阅读:
    CMD窗口正确显示UTF-8字符
    《剑指offer》 链表中倒数第k个节点
    《剑指offer》 调整数组顺序使得奇数在偶数前面
    《剑指offer》 大数递增
    《剑指offer》 数值的整数次方
    《剑指offer》 二进制中1的个数
    《剑指offer》 跳台阶
    《剑指offer》斐波那契数列
    《剑指offer》旋转数组中的最小数字
    刷《剑指offer》笔记
  • 原文地址:https://www.cnblogs.com/Serene-shixinyi/p/7755456.html
Copyright © 2020-2023  润新知