DP思路
在这里记录一些在大神们的博客,以及自己做过的一些DP的神奇思路吧
1.2015/04 NEUQ 月赛 转自:http://zyfzyf.is-programmer.com/posts/89993.html
E.又被DP卡住了。 感觉是必须记录和的具体差值的。因为只有最值无法保证子问题最优的性质。 当然如果既记录具体差值,又记录交换了多少次的话不仅MLE而且TLE。 然后我就弃疗了,觉得一定是防AK神题! 然后学姐发了题解,顿时感觉好巧妙啊。 我们只记录上面的和。用f[i][j]表示前i个数组成和为j最少需要交换多少次,这个满足子问题最优的性质,而且可以很简单的DP。 太神了,Orz! 这次是败在了状态的设计上,以后要多下功夫。(好像脑袋里闪过这个思路?但立马被否决了?) 因为E比较好,所以搬运一下题面: 陈船长和xzx每人都有N个玩具,序号从1到N。不同的玩具可能并不一样好玩。 其中陈船长的第i个玩具的好玩度为C[i],xzx的第i个玩具的好玩度为X[i], 好玩度可以是负数,可以理 解为不好玩的程度。 你可以交换陈船长的第i个玩具和xzx的第i个玩具,为了让他们两个玩的尽可能一样高兴,他们希望经过 不大于K次交换后陈船长所有玩具的好玩度总和与xzx的总和之差的绝对值最小,即 |Sum(C) - Sum(X)| 最小。现在,请你告诉他俩最小值是多少。 多组测试数据,第一行一个整数T(T < 20), 表示测试数据组数。 每组数据第一行两个整数N(0 < N < 100), K(0 < K <= N)。 接下来一行N个整数,第i个数代表C[i](-100 <= C[i] <= 100)。 再接下来一行N个整数, 第i个数代表X[i](-100 <= X[i] <= 100)。 数组下标从0开始。 比赛地址
2. 【模拟试题1】【20150514】
三种物品的背包:
1. $v(x)=A*x^2-B*x$ 价值随所分配的体积的变化而变化……
2. 多重背包
3. 完全背包
其实是个傻逼题,因为数据规模小,暴力就能过,然而由于没见过第一种物品的价值函数,加上题目描述不清楚,所以自己傻逼了(不知道怎么乱搞了一下,得了70分)
其实第一种物品就暴力枚举每一个物品我们分配给它多少空间就可以,“每个体积的甲类物品只有一个”TM就是在误导人!
不过倒是学习了一种新的写背包的姿势~
1 //2015_05_14 pack 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<iostream> 6 #include<algorithm> 7 #define rep(i,n) for(int i=0;i<n;++i) 8 #define F(i,j,n) for(int i=j;i<=n;++i) 9 #define D(i,j,n) for(int i=j;i>=n;--i) 10 #define pb push_back 11 using namespace std; 12 typedef long long LL; 13 inline int getint(){ 14 int r=1,v=0; char ch=getchar(); 15 for(;!isdigit(ch);ch=getchar()) if (ch=='-') r=-1; 16 for(; isdigit(ch);ch=getchar()) v=v*10-'0'+ch; 17 return r*v; 18 } 19 const int N=1010,M=2010; 20 /*******************template********************/ 21 int n,m; 22 LL f[M]; 23 24 void solve1(int a,int b){ 25 D(j,m,1) F(i,0,j) 26 f[j]=max(f[j],f[j-i]+a*i*i-b*i); 27 } 28 void zeroone(int a,int b){ 29 D(j,m,b) 30 f[j]=max(f[j],f[j-b]+a); 31 } 32 void solve2(int a,int b,int c){ 33 int k=0; 34 while(c>=(1<<k)){ 35 zeroone(a*(1<<k),b*(1<<k)); 36 c-=(1<<k); 37 k++; 38 } 39 if (c) zeroone(a*c,b*c); 40 } 41 void solve3(int a,int b){ 42 F(j,b,m) 43 f[j]=max(f[j],f[j-b]+a); 44 } 45 int main(){ 46 #ifndef ONLINE_JUDGE 47 freopen("pack.in","r",stdin); 48 freopen("pack.out","w",stdout); 49 #endif 50 n=getint(); m=getint(); 51 F(i,1,n){ 52 int x=getint(), A=getint(), B=getint(),C; 53 if (x==1){ 54 solve1(A,B); 55 }else if (x==2){ 56 C=getint(); 57 solve2(A,B,C); 58 }else{ 59 solve3(A,B); 60 } 61 } 62 printf("%d\n",f[m]); 63 return 0; 64 }
3. 【BestCoder】【Round#41】 & 【BZOJ】【3612】【HEOI 2014】平衡
整数拆分的DP姿势= =
f[i][j]表示将 i 拆成 j 个不同的数(均不超过n)的方案数
f[i][j]=f[i-j][j]+f[i-j][j-1]
if (i>n+1) f[i][j]-=f[i-(n+1)][j-1](相当于去掉了最后那一列/一个数(n+1))
4.【BZOJ】【1089】【SCOI2003】严格n元树
f[i]=f[i-1]^n+1 f[i]表示深度小于等于 i 的严格n元树的数目
ans=f[d]-f[d-1] //这个表达式感觉好神……
5.【BZOJ】【2750】【HAOI2012】Road && CTSC D1T1 好朋友
拓扑序DP,然而2750这题的DP是较简单的一个,统计每条边在多少条最短路径上……其实可以统计 有多少条最短路径经过了x,以及y出发到达任意一个结束点有多少种走法(沿最短路)
我们可以用Dijkstra求出以 i 为起点的最短路径图,它是一个DAG,然后我们用dij扩展的顺序(一个拓扑序)来搞DP!
令a[x]表示从 i 沿最短路走到 x 的方案数,b[x]表示从 x 往出走,沿最短路走到任意一个结束结点的总方案(就是经过x的最短路条数?只不过只看后半段)
而CTSC的我还没搞出来……
UPD:
答案表达式是这样的:$$ ans[v]=\sum_{s \not = v \not =t} \frac{a[s]*a[t]*\sigma_{s,t}(v)}{\sigma_{s,t}} $$
f1[x]表示从S到x的最短路宽度,然后我们需要处理的是最短路的后一半。。
如果没有分母那个东西,f2[x]应该是 $\sum (f2[to[i]]+a[to[i]])*width[i] $
然而分母和分子是不可以分开搞的……
但是分母其实我们已经算出来了!!就是f1[x]!(我是sb一直没想到……)
所以维护f2[x]还是很容易……$f2[x]=\sum (f2[to[i]]+\frac{a[to[i]]}{f1[to[i]]})*width[i]$
sad....我需要吃药。。。脑残片QAQ
1 /************************************************************** 2 Problem: 4055 3 User: Tunix 4 Language: C++ 5 Result: Accepted 6 Time:6976 ms 7 Memory:1520 kb 8 ****************************************************************/ 9 10 #include<queue> 11 #include<cstdio> 12 #include<cstring> 13 #include<cstdlib> 14 #include<iostream> 15 #include<algorithm> 16 #define rep(i,n) for(int i=0;i<n;++i) 17 #define F(i,j,n) for(int i=j;i<=n;++i) 18 #define D(i,j,n) for(int i=j;i>=n;--i) 19 #define pb push_back 20 using namespace std; 21 typedef long long LL; 22 inline int getint(){ 23 int r=1,v=0; char ch=getchar(); 24 for(;!isdigit(ch);ch=getchar()) if (ch=='-') r=-1; 25 for(; isdigit(ch);ch=getchar()) v=v*10-'0'+ch; 26 return r*v; 27 } 28 //#define debug 29 const int N=1010,M=4010,INF=1000000000; 30 /*******************template********************/ 31 32 33 34 typedef long double db; 35 int head[N],next[M<<1],to[M<<1],len[M<<1],cnt; 36 db b[M<<1]; 37 void ins(int x,int y,int z,db wd){ 38 to[++cnt]=y; next[cnt]=head[x]; head[x]=cnt; len[cnt]=z; b[cnt]=wd; 39 } 40 void add(int x,int y,int z,db wd){ 41 ins(x,y,z,wd); ins(y,x,z,wd); 42 } 43 int n,m,a[N]; 44 typedef pair<int,int> pii; 45 #define mp make_pair 46 priority_queue<pii,vector<pii>,greater<pii> >Q; 47 int c[N],d[N],vis[N]; 48 db ans[N],f1[N],f2[N]; 49 50 51 void Dij(int S){ 52 F(i,1,n) d[i]=INF,vis[i]=0,c[i]=0; 53 d[S]=0; Q.push(mp(0,S)); 54 int t=0; 55 while(!Q.empty()){ 56 int x=Q.top().second; Q.pop(); 57 if (vis[x]) continue; 58 vis[x]=++t; 59 for(int i=head[x];i;i=next[i]) 60 if (d[to[i]]>d[x]+len[i]){ 61 d[to[i]]=d[x]+len[i]; 62 Q.push(mp(d[to[i]],to[i])); 63 } 64 } 65 F(i,1,n) f1[i]=f2[i]=0; f1[S]=1; 66 F(i,1,n) c[vis[i]]=i; 67 // F(i,1,t) printf("%d ",c[i]); puts(""); 68 F(i,1,t){ 69 int x=c[i]; 70 for(int j=head[x];j;j=next[j]) 71 if (d[to[j]]==d[x]+len[j]) f1[to[j]]+=f1[x]*b[j]; 72 } 73 D(i,t,1){ 74 int x=c[i]; 75 for(int j=head[x];j;j=next[j]) 76 if (d[to[j]]==d[x]+len[j]) 77 f2[x]+=b[j]*(f2[to[j]]+(db)a[to[j]]/(db)f1[to[j]]); 78 } 79 F(i,1,n) if (i!=S) ans[i]+=(db)a[S]*f1[i]*f2[i]; 80 } 81 int main(){ 82 #ifndef ONLINE_JUDGE 83 freopen("misc.in","r",stdin); 84 freopen("misc.out","w",stdout); 85 #endif 86 n=getint(); m=getint(); 87 F(i,1,n) a[i]=getint(); 88 F(i,1,m){ 89 int x=getint(),y=getint(),z=getint(); 90 double t1; scanf("%lf",&t1); 91 add(x,y,z,t1); 92 } 93 F(i,1,n) 94 Dij(i); 95 F(i,1,n){ 96 double x=ans[i]; 97 printf("%.9f\n",x); 98 } 99 return 0; 100 }
6.树形DP【模拟试题3】【20150527】
(1)树形状压DP,其实在看题解之前我似乎并没有搞懂这题在干什么……
对于节点 i ,我们考虑f[i][j]表示 i 这棵子树中,分部包含情况为 j 的最大收益,因为一个分部管的是从x到根的所有点,所以对于一个点来说,管它的就是子树中的所有分部,所以就可以dp啦~依次枚举每个儿子的子树中有哪些分部,用一个辅助数组,我们可以搞:
c[j]=f[x][j];
c[j]=max(c[j],f[y][k]+f[x][j^k]);
f[x][j]=c[j];
依次搞下来即可。
初始化就是分部全部在 x 节点。计算完后要加上符合的收益(T种中的某一些……)
(2)无向图最大权路径覆盖= =?然而其实并不是费用流之类……
f[x][0]表示x是孤立点时,x子树内最大收益;
f[x][1]表示x子树内有一条链与它相连;
f[x][2]表示x子树内有两条链与它相连
前两种情况是可以继续向上连的,而第三种情况就不可以了……
转移……Orz regina8023 其实是个贪心。
先假设所有点都没跟x连,得到f[x][0],然后再考虑一下将某个儿子连到父亲,会使答案改变多少,贪心地选出1-2个,即可得到f[x][1]和f[x][2]。
感觉这个先假设全选、【求差】然后在差值中选最优的思路好神啊。。。(其实是我太弱了没见过
7.【BZOJ】【2878】【NOI2012】迷失游乐园
期望&树形&基环树DP的好题QAQ,题解太长我就不复制了,感觉这个先求出来down再求up的方法好神奇……
8.【TYVJ 五月图论专项有奖比赛】
第三题:求树的双重心?。。。树形dp很神奇,Orz zyf。
枚举断掉的点,然后找最大的儿子往过走,更新答案。。。QwQ说不清了
9.【BZOJ】【4145】【AMPPZ2014】The Prices
状压->01背包