由于本蒟蒻的动态规划实在是太弱啦,所以有必要爆破一下洛谷提高试炼场。里面有很多非常好,难度也合适的动态规划题……(然而你还是抄了不少题解)
niconiconi~让我们一起开始爆破吧。
难度评分:※※※
题目分析:
如果你做过洛谷P2858的话,相信这道题的状态转移方程还是很吼写的。首先把每一行分开考虑,f(i,j)表示取完第i到第j个数的最大得分,那么:
#include
#include
#include
#include
using namespace std;
#define LL long long
#define ri register int
const LL mod=1e12;//压位!
struct node{int l;LL t[205];}f[82][82],ans,a[82],bin[82];
int n,m;
void print(node tmp) {//输出
printf("%lld",tmp.t[tmp.l]);
for(ri i=tmp.l-1;i>=1;--i) printf("%012lld",tmp.t[i]);
putchar('\n');
}
node operator + (node a,node b) {//加法
node c;c.l=min(a.l,b.l);LL x=0;
for(ri i=1;i<=a.l&&i<=b.l;++i) {
c.t[i]=a.t[i]+b.t[i]+x;
x=c.t[i]/mod,c.t[i]%=mod;
}
while(c.lwhile(c.lwhile(x) c.t[++c.l]=x,x=c.t[c.l]/mod,c.t[c.l]%=mod;
return c;
}
node operator * (node a,node b) {//乘法
node c;c.l=a.l+b.l;LL x=0;
for(ri i=1;i<=c.l;++i) c.t[i]=0;
for(ri i=1;i<=a.l;++i) {
x=0;
for(ri j=1;j<=b.l;++j) {
c.t[i+j-1]+=a.t[i]*b.t[j]+x;
x=c.t[i+j-1]/mod,c.t[i+j-1]%=mod;
}
c.t[i+b.l]+=x;
}
while(!c.t[c.l]&&c.l>1) --c.l;
return c;
}
bool operator < (node a,node b) {//比较函数
if(a.l>b.l) return 0;
if(a.lreturn 1;
for(ri i=a.l;i>=1;--i)
if(a.t[i]>b.t[i]) return 0;
else if(a.t[i]return 1;
return 1;
}
void init() {//打表2的次方
bin[0].l=1,bin[0].t[1]=1;
node x;x.l=1,x.t[1]=2;
for(ri i=1;i<=m;++i) bin[i]=bin[i-1]*x;
}
int main()
{
scanf("%d%d",&n,&m);ans.l=1;init();
while(n--) {
for(ri j=1;j<=m;++j)
a[j].l=1,scanf("%lld",&a[j].t[1]),f[j][j]=a[j]*bin[m];
for(ri i=m;i>=1;--i)//在重载运算符后,直接dp即可
for(ri j=i+1;j<=m;++j) {
f[i][j]=max(f[i+1][j]+a[i]*bin[m+i-j],f[i][j-1]+a[j]*bin[m+i-j]);
}
ans=ans+f[1][m];
}
print(ans);
return 0;
}
难度评分:※※※
题目分析&代码:
本蒟蒻的另一篇博客
难度评分:※※
题目分析:
这题……不是贪心吗?
考虑深度最大的叶子节点,它可以被它的兄弟,它自己,它的父亲或是爷爷控制。可以发现,它的爷爷可以控制它自己,它的父亲和它的兄弟,所以在爷爷处建立消防局最优。
按深度从大到小处理,对于每一个没有被控制的点,在它爷爷处建立消防局。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int read(){
int w=1,q=0;char ch=' ';
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch>='0'&&ch<='9')q=q*10+ch-'0',ch=getchar();
return w*q;
}
int n,tot,dee,ans;
vector<int>cen[1005];
int ne[2005],h[1005],go[2005],fa[1005];
bool vis[1005];
void add(int x,int y){go[++tot]=y;ne[tot]=h[x];h[x]=tot;}
void dfs(int x,int las,int dep){
dee=max(dee,dep);fa[x]=las;
cen[dep].push_back(x);
for(int i=h[x];i;i=ne[i])
if(go[i]!=las)dfs(go[i],x,dep+1);
}
void bfs(int x,int bs,int las){
vis[x]=1;if(bs==2)return;
for(int i=h[x];i;i=ne[i])
bfs(go[i],bs+1,x);
}
int main()
{
int i,j,x;
n=read();
for(i=2;i<=n;i++){j=read();add(i,j);add(j,i);}
dfs(1,0,1);
for(i=dee;i>=1;i--)
for(j=0;jif(vis[x])continue;
ans++;
if(fa[fa[x]])bfs(fa[fa[x]],0,0);
else if(fa[x])bfs(fa[x],0,0);
else bfs(x,0,0);
}
printf("%d",ans);
return 0;
}
难度评分:※※※※
题目分析:
啊呀洛谷的数据范围开小了啦,搞得暴力都能过。这其实应该是一个有关未来费用的区间dp问题。
如果大家对这一类问题感兴趣的花,还可以看看:修长城,送披萨
由于先往左边走一段关了一盏灯再跑到右边关一盏灯什么的很蠢,所以关的灯一定是一段连续的区间,并且关完灯后处于这个区间的左边或右边。所以我们用f(i,j,0)表示关完[i,j]区间后处于左边,f(i,j,1)表示关完[i,j]区间后处于右边,我们在处理一段区间的时候同时算上所有灯因为没有关灯而花费的费用。
我们用w(i,j)表示除了[i,j]以外的灯功率和(用前缀和维护或预处理)
f(i,j,0)=
min(f(i+1,j,0)+w(i+1,j)∗(di+1−di),f(i+1,j,1)+w(i+1,j)∗(dj−di))
f(i,j,1)=
min(f(i,j−1,0)+w(i,j−1)∗(dj−di),f(i,j−1,1)+w(i,j−1)∗(dj−dj−1))
代码:
其实此题比较推荐记忆化搜索
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int n,m;
long long d[1005],w[1005],r[1005][1005];
long long f[1005][1005][2],ans;
int main()
{
int i,j;
long long x;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
scanf("%lld%lld",&d[i],&x);w[i]=w[i-1]+x;
}
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)r[i][j]=w[i-1]+w[n]-w[j];
memset(f,127/3,sizeof(f));
f[m][m][1]=f[m][m][0]=0;
for(i=n-1;i>=1;i--)
for(j=i+1;j<=n;j++){
f[i][j][0]=min((long long)f[i+1][j][0]+r[i+1][j]*(d[i+1]-d[i]),
(long long)f[i+1][j][1]+r[i+1][j]*(d[j]-d[i]));
f[i][j][1]=min((long long)f[i][j-1][0]+r[i][j-1]*(d[j]-d[i]),
(long long)f[i][j-1][1]+r[i][j-1]*(d[j]-d[j-1]));
}
ans=min(f[1][n][1],f[1][n][0]);
printf("%lld",ans);
return 0;
}
难度评分:※
题目分析:
用f(i,j)表示处理序列中[i,j]这一段的最大加分,由于中序遍历一定是(左子树)(根)(右子树),所以我们枚举这一段子树中根在哪儿,实现状态转移,具体方程为:
#include
#include
#include
#include
#include
using namespace std;
int n;
int a[31],fa[31][31];
long long f[31][31];
void print(int now,int l,int r){
printf("%d ",now);
if(now-1>=l)print(fa[l][now-1],l,now-1);
if(now+1<=r)print(fa[now+1][r],now+1,r);
}
int main()
{
int i,j,k;
long long ans=0;
scanf("%d",&n);
for(i=1;i<=n;i++) { scanf("%d",&a[i]);f[i][i]=a[i];fa[i][i]=i;}
for(i=n-1;i>=1;i--){
for(j=i+1;j<=n;j++){
if(f[i][j]1][j]+a[i])
f[i][j]=f[i+1][j]+a[i],fa[i][j]=i;
if(f[i][j]1]+a[j])
f[i][j]=f[i][j-1]+a[j],fa[i][j]=j;
for(k=i+1;k<=j-1;k++){
ans=f[i][k-1]*f[k+1][j]+a[k];
if(f[i][j]printf("%lld\n",f[1][n]);
print(fa[1][n],1,n);
return 0;
}
难度评分:※※
题目分析:
树上背包问题,几乎是裸题。
但是数据加强后好像过不去了desi……
代码:
#include
#include
#include
#include
#include
using namespace std;
int read(){
int w=1,q=0;char ch=' ';
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch>='0'&&ch<='9')q=q*10+ch-'0',ch=getchar();
return w*q;
}
int n,m,tot;
int ne[6005],h[3005],go[6005],coss[6005],mon[3005];
int f[3005][3005];
void add(int x,int y,int z){
go[++tot]=y;coss[tot]=z;
ne[tot]=h[x];h[x]=tot;
}
int dfs(int x){
int i,j,k,kl=0;
if(x>n-m){f[x][1]=mon[x];return 1;}
for(i=h[x];i;i=ne[i])
kl+=dfs(go[i]);
for(i=h[x];i;i=ne[i])
for(j=kl;j>=1;j--){
for(k=1;k<=j;k++)
f[x][j]=max(f[x][j],f[x][j-k]+f[go[i]][k]-coss[i]);
}
return kl;
}
int main()
{
int i,j,num,x,y;
n=read();m=read();
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)f[i][j]=-99999999;
for(i=1;i<=n-m;i++){
num=read();
for(j=1;j<=num;j++){
x=read();y=read();
add(i,x,y);
}
}
for(i=n-m+1;i<=n;i++)mon[i]=read();
x=dfs(1);
for(i=m;i>=0;i--)
if(f[1][i]>=0){printf("%d",i);return 0;}
return 0;
}
难度评分:※
题目分析:
令f(i,j)表示垃圾堆了i高度时卡门还可以活j小时是否可以实现。
然后就有堆放垃圾和吃垃圾两种操作,转移还是很easy的吧。
代码:
两年前的老代码了……风格清奇……两格缩进,超多头文件,超多大括号嵌套,cin和cout读入输出,手打冒泡排序……
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
bool f[400][6000]={0};
struct lj{
int time;
int h;
int e;
}lj[101];
int main()
{
int n,m=10,d,k,i,j;
cin>>d>>n;
for(i=1;i<=n;i++)
{
cin>>lj[i].time>>lj[i].e>>lj[i].h;
m+=lj[i].e;
}
for(i=1;i<=n-1;i++)//两年前的我手打的冒泡排序QAQ
for(j=1;j<=n-i;j++)
{
if(lj[j+1].time0]=lj[j];lj[j]=lj[j+1];lj[j+1]=lj[0];
}
}
f[0][10]=1;
for(i=1;i<=n;i++)
{
for(k=d;k>=0;k--)
for(j=m;j>=lj[i].time;j--)
{
if(f[k][j]==1)
{
if(k+lj[i].h>=d)
{
cout<return 0;
}
f[k+lj[i].h][j]=1;
f[k][j+lj[i].e]=1;
}
}
}
i=m;
while(f[0][i]==0)i--;
cout<return 0;
}
难度评分:※※※※
题目分析&代码:戳我n(≧▽≦)n
难度评分:※※※※
题目分析:
感谢aiyougege在洛谷发布的题解。
对于一个排列,我们只关心每一个数的相对大小。所以我们可以令f(i,j)表示一个i个元素的排列,第一个数是上升的,并且第i个数在这i个数中的相对大小为j时的方案数。
现在我们需要几个结论:
1.在n->n-1的转化过程中,我们删除了一个点后,我们可以将n-1个点视为仍是1~n-1的排列。
2.在若排列Pn为一个合法抖动子序列,则交换i∈[1,n)与i+1,必能得到另一个抖动子序列。
3.抖动序列的对称性,若存在第一段上升的长度为n的抖动子序列,则以n+1-x代x必能得到一个第一段下降的长度为n的抖动子序列。
都很显然。
所以最后答案是f(n,n)*2
转移方程呢?我们考虑j与j-1是否相邻。
如果不相邻:将j-1与j交换显然排列个数不变,这种情况有f(i,j-1)种情况
如果相邻:那么我们需要一个i-1个元素的最后一个元素是j-1,第一个元素是下降的序列方案数,这样第j-1个元素在i-1个元素中的相对大小应该为j-1,又因为要求下降序列,根据结论3,所以可以得到方案数为f(i-1,i-j+1)
代码:
#include
#include
#include
#include
using namespace std;
#define LL long long
int n;LL m,ans,f[2][4205];
int main()
{
scanf("%d%lld",&n,&m);
f[1][1]=1;
for(int i=2;i<=n;++i) {
int t=i&1;
for(int j=1;j<=i;++j)
f[t][j]=(f[t][j-1]+f[t^1][i-j+1])%m;
}
for(int i=1;i<=n;++i) ans=(ans+f[n&1][i])%m;
printf("%lld",(ans+ans)%m);
return 0;
}
难度评分:※※※※
题目分析:
两次dp,一次d(i)表示数列中第i个数为结尾时,最后一个数最小,最后一个数开头的位置,dp方法见代码work1。
一次f(i)表示数列中第i个数为开头时,第一个数最大,第一个数结尾的位置,dp方法见work2。不过单纯地dp是不行的,因为对于最后一个数有前导0的情况,d(i)并不会处理,所以应该在work2中做处理。
代码:
#include
#include
#include
#include
using namespace std;
const int N=505;
int n,a[N],f[N],d[N];
char s[N];
int bj(int l1,int r1,int l2,int r2) {//比较大小的函数
if(!r1) return 1;
while(!a[l1]&&l1while(!a[l2]&&l2if(r1-l1+1>r2-l2+1) return 0;
if(r1-l1+11) return 1;
for(int i=0;i<=r1-l1;++i)
if(a[l1+i]>a[l2+i]) return 0;
else if(a[l1+i]return 1;
return 0;
}
void work1() {
for(int i=1;i<=n;++i)
for(int j=i;j>=1;--j)
if(bj(d[j-1],j-1,j,i)){d[i]=j;break;}
}
void work2() {
f[d[n]]=n;int zero=d[n];
while(zero>1&&!a[zero-1]) f[zero-1]=n,--zero;//看一看最后一个数的前导0
for(int i=zero-1;i>=1;--i)
for(int j=d[n]-1;j>=i;--j)
if(bj(i,j,j+1,f[j+1])){f[i]=j;break;}
}
int main()
{
scanf("%s",s);n=strlen(s);
for(int i=1;i<=n;++i) a[i]=s[i-1]-'0';
work1();work2();
for(int x=1,y;x<=n;x=y) {
y=x;while(y<=f[x]) printf("%d",a[y]),++y;
if(y<=n) putchar(',');
}
return 0;
}
难度评分:※※※※※
题目分析:
用f(i,x1,x2)表示考虑了前i行,有x1列放了一个棋子,有x2列放了两个棋子。
然后状态转移方程,好像,难打。
第i行不放棋子:
#include
#include
#include
#include
using namespace std;
#define LL long long
LL mod=9999973;
int n,m;LL f[105][105][105],ans;
LL qm(LL x){return x>=mod?x-mod:x;}
int main()
{
scanf("%d%d",&n,&m);
f[0][0][0]=1;
for(int i=1;i<=n;++i)
for(int x1=0;x1<=m;++x1)
for(int x2=0;x2+x1<=m;++x2) {
f[i][x1][x2]=f[i-1][x1][x2];
if(x1) f[i][x1][x2]=qm(f[i][x1][x2]+f[i-1][x1-1][x2]*(m-x1-x2+1)%mod);
if(x2) f[i][x1][x2]=qm(f[i][x1][x2]+f[i-1][x1+1][x2-1]*(x1+1)%mod);
if(x1>=2) f[i][x1][x2]=qm(f[i][x1][x2]+(LL)(m-x1-x2+2)*(m-x1-x2+1)/2*f[i-1][x1-2][x2]%mod);
if(x2) f[i][x1][x2]=qm(f[i][x1][x2]+f[i-1][x1][x2-1]*(m-x1-x2+1)*x1%mod);
if(x2>=2) f[i][x1][x2]=qm(f[i][x1][x2]+(LL)(x1+2)*(x1+1)/2*f[i-1][x1+2][x2-2]%mod);
if(i==n) ans=qm(ans+f[i][x1][x2]);
}
printf("%lld",ans);
return 0;
}
难度评分:※※※※※
题目分析:
状态压缩dp,用f(i,zt,j)表示当前处理到第i个人,第i个人及其后面7个人是否打饭的情况,上一个打饭的人是第i+j个人的时候的最优解。 −8≤j≤7 ,所以在使用数组储存的时候要加8.
然后枚举下一个打饭的人(必须合法)即可实现状态转移,在zt中表示第i个人已经打了饭的时候,要转移至f(i+1,zt/2,j)
具体方程见代码喵。
代码:
#include
#include
#include
#include
using namespace std;
#define ri register int
const int N=1005,inf=1061109567;
int n,T,ans;
int w[N],b[N],bin[10],f[N][260][16];
void chk(int &x,int y){x=(yint main()
{
scanf("%d",&T);
bin[0]=1;for(ri i=1;i<=8;++i) bin[i]=bin[i-1]<<1;
while(T--) {
scanf("%d",&n);
for(ri i=1;i<=n;++i) scanf("%d%d",&w[i],&b[i]);
memset(f,0x3f,sizeof(f));f[1][0][7]=0,ans=inf;
for(ri i=1;i<=n;++i)
for(ri zt=0;zt8];++zt)
for(ri j=0;j<=15;++j) {
if(f[i][zt][j]==inf) continue;
if(zt&bin[0]) {chk(f[i+1][zt>>1][j-1],f[i][zt][j]);}//实现i系列向i+1系列的转移
else {
int lim=inf;
for(ri k=0;k<=b[i]&&k+i<=lim;++k) {
if(zt&bin[k]) continue;
if(i+k>n) continue;
lim=min(lim,b[k+i]+k+i);//记录最多到谁还可以打饭
int t1=w[i+(j-8)],t2=w[i+k],c=(i+(j-8)==0?0:(t1|t2)-(t1&t2));
chk(f[i][zt|bin[k]][k+8],f[i][zt][j]+c);//状态转移
}
}
}
for(ri i=0;i<=8;++i) ans=min(ans,f[n+1][0][i]);
printf("%d\n",ans);
}
return 0;
}
难度评分:※※
题目分析:
这题真的是动态规划吗?
不是几乎裸的滑动窗口类单调队列题吗?
对每一行维护两个单调队列,记录该行某一列为右端,长为n的滑动窗口中的最大值和最小值。
然后对每一列维护两个单调队列,记录该列某一行为下端,长为n的滑动窗口,上一次维护结果的最大值和最小值即可得到答案。
代码:
#include
#include
#include
#include
using namespace std;
const int N=1505;
int a,b,n,ans=2e9+8;
int w[N][N],mi[N][N],mx[N][N],qmi[N],qmx[N];
int main()
{
scanf("%d%d%d",&a,&b,&n);
for(int i=1;i<=a;++i)
for(int j=1;j<=b;++j) scanf("%d",&w[i][j]);
for(int i=1;i<=a;++i) {
int hmi=1,hmx=1,tmi=1,tmx=1;
for(int j=1;j<=b;++j) {
while(hmi1]]>=w[i][j]) --tmi;
qmi[tmi++]=j;
while(hmx1]]<=w[i][j]) --tmx;
qmx[tmx++]=j; if(jcontinue;
while(hmiwhile(hmxfor(int j=n;j<=b;++j) {
int hmi=1,hmx=1,tmi=1,tmx=1;
for(int i=1;i<=a;++i) {
while(hmi1]][j]>=mi[i][j]) --tmi;
qmi[tmi++]=i;
while(hmx1]][j]<=mx[i][j]) --tmx;
qmx[tmx++]=i; if(icontinue;
while(hmiwhile(hmxprintf("%d",ans);
return 0;
}
难度评分:※※※※※
题目分析:
讲完了有两列的情况,只有一列的情况肯定会了。
f(zt,i,j)表示当前行的选择状态为zt,前i行选择j个子矩阵的最优解。
zt表示的含义:
0:不选该行,1:选择第一列,2:选择第二列,3:第一列和第二列都选择,且在同一个子矩阵中,4:第一列和第二列都选择,且分别位于两个子矩阵中。
然后转移……大概有25条转移语句,看代码吧,真不想再写一遍了。
代码:
调了一晚上,一度崩溃
#include
#include
#include
#include
using namespace std;
int n,m,num,ans,kl;
int f[5][105][11],a[105][3];
//0:空出,1:第一列,2:第二列,3:都选(一起),4:都选(不一起)
int main()
{
scanf("%d%d%d",&n,&m,&num);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j) scanf("%d",&a[i][j]);
if(m==1) {
for(int i=1;i<=n;++i)
for(int k=1;k<=num;++k) {
f[0][i][k]=max(f[0][i-1][k],f[1][i-1][k]);
f[1][i][k]=max(f[1][i-1][k],f[0][i-1][k-1])+a[i][1];
}
printf("%d",max(f[1][n][num],f[0][n][num]));
}
else {
memset(f,-0x3f,sizeof(f));
for(int i=0;i<=n;++i)
for(int j=0;j<=num;++j) f[0][i][j]=0;
for(int i=1;i<=n;++i)
for(int k=1;k<=num;++k) {
f[0][i][k]=max(f[0][i-1][k],max(f[1][i-1][k],f[2][i-1][k]));
f[0][i][k]=max(f[0][i][k],max(f[3][i-1][k],f[4][i-1][k]));
f[1][i][k]=max(max(f[0][i-1][k-1],f[1][i-1][k]),f[2][i-1][k-1]);
f[1][i][k]=max(max(f[3][i-1][k-1],f[4][i-1][k]),f[1][i][k])+a[i][1];
f[2][i][k]=max(max(f[0][i-1][k-1],f[1][i-1][k-1]),f[2][i-1][k]);
f[2][i][k]=max(max(f[3][i-1][k-1],f[4][i-1][k]),f[2][i][k])+a[i][2];
f[3][i][k]=max(max(f[0][i-1][k-1],f[1][i-1][k-1]),f[2][i-1][k-1]);
f[3][i][k]=max(max(f[3][i-1][k],f[4][i-1][k-1]),f[3][i][k])+a[i][1]+a[i][2];
f[4][i][k]=max(max(f[1][i-1][k-1],f[2][i-1][k-1]),f[4][i-1][k]);
if(k>=2) f[4][i][k]=max(max(f[3][i-1][k-2],f[0][i-1][k-2]),f[4][i][k]);
f[4][i][k]+=a[i][1]+a[i][2];
}
ans=max(f[0][n][num],max(f[1][n][num],f[2][n][num]));
ans=max(ans,max(f[3][n][num],f[4][n][num]));
printf("%d",ans);
}
return 0;
}
本蒟蒻爆破了试炼场后,并改不了自己还是很蒻的事实,总之,会继续fighting的!