----19.7.30 今天又开了一个新专题,dp杂题,我依旧按照之前一样,这一个专题更在一起,根据个人进度选更题目;
dp就是动态规划,本人认为,动态规划的核心就是dp状态的设立以及dp转移方程的推导,这也是训练的重中之重,所以代码不那么重要,重要的就是dp的思想;
T1:
A. 消失之物
题目描述
ftiasch 有 N 个物品, 体积分别是 W1, W2, ..., WN。 由于她的疏忽, 第 i 个物品丢失了。 “要使用剩下的 N - 1 物品装满容积为 x 的背包,有几种方法呢?” -- 这是经典的问题了。她把答案记为 Count(i, x) ,想要得到所有1 <= i <= N, 1 <= x <= M的 Count(i, x) 表格。
这道题乍一看以为就是一个裸的背包,但是(本人太弱了)我以上来先想到的是裸的$ 0/1 $背包然后就想跑$ n $遍$ 0/1 $背包,但是显然复杂度会爆炸,所以要考虑别的方法,但是还是逃不掉的dp,重点就在状态的转移,这个转移其实可以在之前的0/1背包的基础上进行转移,那么我们可以设f[maxn][2],也就是开一维半的数组,设状态为$ f[j][1] $表示背包容量为j时的方案数,因为题目恶心了我们就是要输出一个矩阵,那么我们就需要再循环i表示当我们去掉i时$ f[j][1] $就是背包容量为j时的方案数,那么就可以列出状态转移方程:(这里的0/1表示能否可以解决!)
$ f[j][0]+=f[j-v[i]] $
$ f[j][1]=f[j][0]+f[j-v[i]][1] (j-v[i]>0)$
$ f[j][1]=f[j][0] (j-v[i]<=0) $
然后就结束了,一定要记得多多取模(他让输出最低的一位,所以不那么恶心!),这道题没什么细节,就不站代码了,评论区留给你们!
UPD:这道题skyh的打法刷新了我的dp观,我是真的震惊!
天黄的这道题使用分治带dp打的,很新颖,不愧是dalao(orz),那么我也稍说一下skyh的思路:
$ (after 10 mins...) $其实和我的思路差不多,就是分治了一下,qwq
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 const int N=2010; 5 int n,m,w[N]; 6 short dp[15][N]; 7 void solve(int dep,int l,int r){ 8 if(l==r){ 9 for(int i=1;i<=m;++i) printf("%d",dp[dep-1][i]); 10 puts(""); 11 return ; 12 } 13 int mid=l+r>>1; 14 for(int i=0;i<=m;++i) dp[dep][i]=dp[dep-1][i]; 15 for(int i=mid+1;i<=r;++i) for(int j=m;j>=w[i];--j) (dp[dep][j]+=dp[dep][j-w[i]])%=10; 16 solve(dep+1,l,mid); 17 for(int i=0;i<=m;++i) dp[dep][i]=dp[dep-1][i]; 18 for(int i=l;i<=mid;++i) for(int j=m;j>=w[i];--j) (dp[dep][j]+=dp[dep][j-w[i]])%=10; 19 solve(dep+1,mid+1,r); 20 } 21 int main() 22 { 23 scanf("%d%d",&n,&m); 24 for(int i=1;i<=n;++i) scanf("%d",&w[i]); 25 dp[0][0]=1; solve(1,1,n); 26 return 0; 27 }
//copyright by skyh
//copy from skyh orz
B. 方伯伯的玉米田
题目描述
输入格式
输出格式
这是学长讲过的一道例题,是数据结构优化dp,而且这道题需要证明一个引理;
引理:所有操作的右端点一定是n(最右侧)的那个点。
证明:如果将一个区间内的权值都加上一个数,只会出现两种情况:
1.区间的左侧:
之前比区间内的数小的在操作之后还是比他小;
之前比区间内的数大的在操作之后没有他大(比他小!);
之前比区间内的数大的在操作之后还是比他大;
所以区间左侧不会降低ans,还可能增加ans
2.区间的右侧:
之前比区间内的数小在操作之后还是比他小;
之前比区间内数大的,现在不一定比他大;
之前比区间内数大的,现在还是比他大;
所以区间右侧不会升高ans,还可能降低ans;
所以要保证答案最优,就要有区间右侧最小,所以就有所有的操作都以n为有区间的端点;
证明完毕;
接着回到题解,这里有了上面的引理,我们就能推出dp的状态转移方程;
设$ f[i][j] $表示以i为结尾,共被拔高了j次的ans,即以i为结尾,共被j个区间覆盖;
那么,我们根据定义可以推出状态转移方程:
$ f[i][j]=max{f[k][l]}+1 (1<=k<=i,1<=l<=j ) $且要合法才能转移;
那么这一看如果暴力求解的话复杂度爆表,所以这个可以使用二维树状数组进行优化,然后就是$ O(n*m) $的复杂度;
代码实现也很简单:
1 #include<iostream> 2 #include<cstring> 3 #include<cmath> 4 #include<cstdlib> 5 #include<cstdio> 6 using namespace std; 7 #define re register 8 int a[10005],c[10005][505],n,m,maxa,ans,sum,res; 9 int lowbit(int x){return x&(-x);} 10 void change(int x,int y,int z) 11 { 12 int yy=y; 13 while(x<=maxa+m) 14 { 15 y=yy; 16 while(y<=m+1) 17 { 18 c[x][y]=max(c[x][y],z); 19 y+=lowbit(y); 20 } 21 x+=lowbit(x); 22 } 23 } 24 int getsum(int x,int y) 25 { 26 int yy=y,sum=0; 27 while(x>0) 28 { 29 y=yy; 30 while(y>0) 31 { 32 sum=max(sum,c[x][y]); 33 y-=lowbit(y); 34 } 35 x-=lowbit(x); 36 } 37 return sum; 38 } 39 int main() 40 { 41 //freopen("simple.txt","r",stdin); 42 scanf("%d%d",&n,&m); 43 for(int i=1;i<=n;i++) 44 { 45 scanf("%d",&a[i]); 46 maxa=max(maxa,a[i]); 47 } 48 for(int i=1;i<=n;i++) 49 { 50 for(int j=m;j>=0;j--) 51 { 52 res=getsum(a[i]+j,j+1)+1; 53 change(a[i]+j,j+1,res); 54 ans=max(ans,res); 55 } 56 } 57 printf("%d ",ans); 58 return 0; 59 }
C. 拦截导弹
题目描述
输入格式
,表示敌军导弹数量;
下面 行按顺序给出了敌军所有导弹信息:
第i+1行包含2个正整数
和
输出格式
样例
数据范围与提示
这个spj其实是假的,并没有部分分,但是这道题确实是一道好题,其实之前做过拦截导弹的简单版,就是线性dp,所以这还是用线性dp,显然是不行的,因为我以为是spj已经WA0好几次了,因为这道题有三个限制条件,要同时满足三个限制条件,不就是cdq分治吗,然后就把统计答案的dp扔到cdq的过程中,然后,就切了。就这样切了,当然不是,反正我不是1A的,记得开double!
1 #include<algorithm> 2 #include<iostream> 3 #include<cstring> 4 #include<cstdio> 5 #include<cmath> 6 #include<vector> 7 using namespace std; 8 const int maxn=100000; 9 inline int read() 10 { 11 int x=0,f=1;char cc;cc=getchar(); 12 while(cc>'9'||cc<'0'){if(cc=='-')f=-1;cc=getchar();} 13 while(cc>='0'&&cc<='9'){x=(x<<3)+(x<<1)+(cc^48);cc=getchar();} 14 return x; 15 } 16 struct tree 17 { 18 int f;double w; 19 tree(){f=0,w=0;} 20 }t[maxn]; 21 int n; 22 int st[maxn],top=0,th,tv; 23 inline int lowbit(int x){return x&(-x);} 24 inline void add(int p,int f,double w) 25 { 26 while(p<n) 27 { 28 if(t[p].f<f) 29 { 30 if(t[p].f==0)st[++top]=p; 31 t[p].f=f;t[p].w=w; 32 } 33 else if(t[p].f==f)t[p].w+=w; 34 p+=lowbit(p); 35 } 36 return; 37 } 38 tree ask(int p) 39 { 40 tree res; 41 while(p) 42 { 43 if(t[p].f>res.f) res=t[p]; 44 else if(t[p].f==res.f) res.w+=t[p].w; 45 p-=lowbit(p); 46 } 47 return res; 48 } 49 struct Dan 50 { 51 int h,v,f[2],id,t; 52 double g[2]; 53 }a[maxn],q[maxn]; 54 int wh[maxn],wv[maxn],id[maxn]; 55 int rk[maxn]; 56 inline bool cmp(int x,int y){return a[x].h<a[y].h||(a[x].h==a[y].h&&a[x].id<a[y].id);} 57 int cmpid(Dan a,Dan b){return a.id<b.id;} 58 int cnt=0; 59 void cdq(int l,int r,int mode) 60 { 61 if(l==r) 62 { 63 if(a[l].f[mode]<1){a[l].f[mode]=1;a[l].g[mode]=1;} 64 return; 65 } 66 int mid=(l+r)>>1; 67 memcpy(q+l,a+l,sizeof(Dan)*(r-l+1)); 68 int q1=l,q2=mid+1; 69 for(int i=l;i<=r;i++) 70 if(q[i].t<=mid)a[q1++]=q[i]; 71 else a[q2++]=q[i]; 72 cdq(l,mid,mode); 73 q1=l; 74 for(int i=mid+1;i<=r;i++) 75 { 76 while(q1<=mid && a[q1].id<a[i].id) 77 add(a[q1].v,a[q1].f[mode],a[q1].g[mode]),q1++; 78 tree res=ask(a[i].v); 79 if(!res.f)continue; 80 if(res.f+1>a[i].f[mode]) 81 { 82 a[i].f[mode]=res.f+1; 83 a[i].g[mode]=res.w; 84 } 85 else if(res.f+1==a[i].f[mode]) a[i].g[mode]+=res.w; 86 } 87 while(top){t[st[top]].w=0;t[st[top--]].f=0;} 88 cdq(mid+1,r,mode); 89 merge(a+l,a+mid+1,a+mid+1,a+r+1,q+l,cmpid); 90 memcpy(a+l,q+l,sizeof(Dan)*(r-l+1)); 91 return; 92 } 93 int main() 94 { 95 //freopen("cnm.txt","r",stdin); 96 n=read(); 97 for(int i=1;i<=n;i++) 98 { 99 a[i].h=read();a[i].v=read();a[i].id=i; 100 wh[i]=a[i].h;wv[i]=a[i].v; 101 rk[i]=i; 102 } 103 sort(wh+1,wh+n+1); 104 sort(wv+1,wv+n+1); 105 th=unique(wh+1,wh+n+1)-wh-1; 106 tv=unique(wv+1,wv+n+1)-wv-1; 107 for(int i=1;i<=n;i++) 108 { 109 a[i].h=th-(lower_bound(wh+1,wh+th+1,a[i].h)-wh)+1; 110 a[i].v=tv-(lower_bound(wv+1,wv+tv+1,a[i].v)-wv)+1; 111 } 112 sort(rk+1,rk+n+1,cmp); 113 for(int i=1;i<=n;i++)a[rk[i]].t=i; 114 cdq(1,n,0); 115 for(int i=1;i<=n;i++) 116 { 117 a[i].h=th-a[i].h+1; 118 a[i].v=tv-a[i].v+1; 119 a[i].id=n-a[i].id+1; 120 a[i].t=n-a[i].t+1; 121 } 122 reverse(a+1,a+n+1); 123 cdq(1,n,1); 124 reverse(a+1,a+n+1); 125 double smm=0; 126 int ans=0; 127 for(register int i=1;i<=n;i++) 128 ans=max(ans,a[i].f[0]+a[i].f[1]-1); 129 printf("%d ",ans); 130 for(int i=1;i<=n;i++) 131 if(a[i].f[0]==ans) 132 smm+=a[i].g[0]*a[i].g[1]*1ll; 133 for(int i=1;i<=n;i++) 134 { 135 double res=a[i].g[0]*a[i].g[1]; 136 if(a[i].f[0]+a[i].f[1]-1!=ans)printf("%.5lf ",0.0); 137 else printf("%.5f ",res/smm); 138 } 139 return 0; 140 }
///////这个专题的坑还很大,未完待续.........////////