[POJ&HDU]杂题记录
POJ2152
树形dp,每次先dfs一遍求出距离再枚举所有点转移即可。
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 1010 int N,T,d[MAXN],c[MAXN]; struct EdgeNode{int next,to,dis;}edge[MAXN<<1]; int head[MAXN],cnt=1; int f[MAXN][MAXN],g[MAXN],deep[MAXN]; inline void AddEdge(int u,int v,int w) {cnt++; edge[cnt].next=head[u]; edge[cnt].to=v; edge[cnt].dis=w; head[u]=cnt;} inline void InsertEdge(int u,int v,int w) {AddEdge(u,v,w); AddEdge(v,u,w);} inline void DFS_1(int now,int last) { for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) deep[edge[i].to]=deep[now]+edge[i].dis, DFS_1(edge[i].to,now); } inline void DFS_2(int now,int last) { for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) DFS_2(edge[i].to,now); deep[now]=0; DFS_1(now,0); for (int j=1; j<=N; j++) if (deep[j]<=d[now]) { f[now][j]=c[j]; for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) f[now][j]+=min(g[edge[i].to],f[edge[i].to][j]-c[j]); g[now]=min(g[now],f[now][j]); } } int main() { T=read(); while (T--) { N=read(); for (int i=1; i<=N; i++) c[i]=read(); for (int i=1; i<=N; i++) d[i]=read(); cnt=1; memset(head,0,sizeof(head)); for (int i=1,x,y,z; i<=N-1; i++) x=read(),y=read(),z=read(),InsertEdge(x,y,z); memset(f,63,sizeof(f)); memset(g,63,sizeof(g)); DFS_2(1,0); // for (int i=1; i<=N; i++) // for (int j=1; j<=N; j++) // printf("%d %d %d\n",i,j,f[i][j]); printf("%d\n",g[1]); } return 0; }
POJ3280
区间dp,最小回文代价
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<map> using namespace std; #define MAXN 3010 int N,M,add[27],del[27],dp[MAXN][MAXN],hash[27]; char s[MAXN],ss[27][2]; int main() { scanf("%d%d%s",&N,&M,s+1); for (int i=1; i<=N; i++) scanf("%s%d%d",ss[i]+1,&add[i],&del[i]),hash[ss[i][1]-'a'+1]=i; // memset(dp,63,sizeof(dp)); for (int i=1; i<=M; i++) dp[i][i]=0; for (int len=2; len<=M; len++) for (int l=1,r=l+len-1; l+len-1<=M; l++,r++) { dp[l][r]=0x3f3f3f3f; if (s[l]==s[r]) dp[l][r]=dp[l+1][r-1]; else dp[l][r]=min(dp[l][r],dp[l+1][r]+min(add[ hash[ s[l]-'a'+1 ] ],del[ hash[ s[l]-'a'+1 ] ])), dp[l][r]=min(dp[l][r],dp[l][r-1]+min(add[ hash[ s[r]-'a'+1 ] ],del[ hash[ s[r]-'a'+1 ] ])); // printf("%d %d %d\n",l,r,dp[l][r]); } printf("%d\n",dp[1][M]); return 0; }
POJ1988
带权并查集
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 300010 int N,f[MAXN],Q,h[MAXN],d[MAXN]; inline int F(int x) { if (f[x]==x) return x; int fx=f[x]; f[x]=F(f[x]); h[x]+=h[fx]; return f[x]; } inline void merge(int x,int y) { int fx=F(x),fy=F(y); if (fx==fy) return; f[fx]=fy; h[fx]=d[fy]; d[fy]+=d[fx]; d[fx]=0; } int main() { Q=N=read(); for (int i=1; i<=30010; i++) f[i]=i,h[i]=0,d[i]=1; while (Q--) { char opt[2]; scanf("%s",opt); int x,y; switch (opt[0]) { case 'M': x=read(),y=read(),merge(x,y); break; case 'C': x=read(); y=F(x); printf("%d\n",h[x]); break; } } return 0; }
POJ1984
带权并查集,把四个方向的运算换成向量运算即可
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<cmath> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 100010 int N,M,Q,ans[MAXN]; struct QNode{int u,v,t,id;}q[MAXN]; struct ENode{int u,v,dis,dir[1];}e[MAXN]; struct Vector { int x,y; inline int Len() {return abs(x)+abs(y);} }V[MAXN]; Vector operator + (Vector A,Vector B) {return (Vector){A.x+B.x,A.y+B.y};} Vector operator - (Vector A,Vector B) {return (Vector){A.x-B.x,A.y-B.y};} inline bool cmp(QNode A,QNode B) {return A.t<B.t;} int f[MAXN]; inline int F(int x) {if (f[x]==x) return x; int y=f[x]; f[x]=F(f[x]); V[x]=V[x]+V[y]; return f[x];} inline void merge(int x,int y,Vector v) { int fx=F(x),fy=F(y); if (fx==fy) return; f[fy]=fx; V[fy]=V[x]-V[y]+v; } inline void Merge(ENode edge) { int x=edge.u,y=edge.v,d=edge.dis; char dir=edge.dir[0]; switch(dir) { case 'N': merge(x,y,(Vector){0,d}); break; case 'E': merge(x,y,(Vector){d,0}); break; case 'S': merge(x,y,(Vector){0,-d}); break; case 'W': merge(x,y,(Vector){-d,0}); break; } } int main() { N=read(); M=read(); for (int i=1; i<=M; i++) e[i].u=read(),e[i].v=read(),e[i].dis=read(),scanf("%s",e[i].dir); Q=read(); for (int i=1; i<=Q; i++) q[i].u=read(),q[i].v=read(),q[i].t=read(),q[i].id=i; sort(q+1,q+Q+1,cmp); for (int i=1; i<=N; i++) f[i]=i; for (int i=1,j=1; i<=Q; i++) { while (j<=q[i].t && j<=M) Merge(e[j++]); int fx=F(q[i].u),fy=F(q[i].v); ans[q[i].id]=fx==fy? (V[q[i].u]-V[q[i].v]).Len() : -1; } for (int i=1; i<=Q; i++) printf("%d\n",ans[i]); return 0; }
HDU5900
区间dp,唯一需要注意的就是转移$[l,r]$整段区间的时候,需要满足$[l+1,r-1]$全部被取到,然后就没有了
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> using namespace std; #define LL long long inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 310 int T,N,a[MAXN],b[MAXN]; LL sum[MAXN],dp[MAXN][MAXN]; inline int Gcd(int a,int b) {if (!b) return a; else return Gcd(b,a%b);} int main() { T=read(); while (T--) { N=read(); for (int i=1; i<=N; i++) a[i]=read(); for (int i=1; i<=N; i++) b[i]=read(),sum[i]=sum[i-1]+b[i]; memset(dp,0,sizeof(dp)); for (int i=1; i<=N-1; i++) dp[i][i+1]=Gcd(a[i],a[i+1])!=1? b[i]+b[i+1]:0; for (int len=3; len<=N; len++) for (int l=1,r=l+len-1; l+len-1<=N; l++,r++) { if (dp[l+1][r-1]==sum[r-1]-sum[l+1-1]) dp[l][r]=dp[l+1][r-1]+(Gcd(a[l],a[r])!=1? b[l]+b[r]:0); for (int k=l; k<=r-1; k++) dp[l][r]=max(dp[l][r],dp[l][k]+dp[k+1][r]); } printf("%lld\n",dp[1][N]); } return 0; }
POJ3162
三次dfs求出各点到直径两端的距离,然后答案可以利用两个指针,用单调队列维护一下。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 1000100 int N,M; struct EdgeNode{int next,to,dis;}edge[MAXN<<1]; int head[MAXN],cnt=1; inline void AddEdge(int u,int v,int w) {cnt++; edge[cnt].to=v; edge[cnt].next=head[u]; edge[cnt].dis=w; head[u]=cnt;} inline void InsertEdge(int u,int v,int w) {AddEdge(u,v,w); AddEdge(v,u,w);} int deep[MAXN],L,side,dis[MAXN]; inline void DFS(int now,int last) { for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) deep[edge[i].to]=deep[now]+edge[i].dis, DFS(edge[i].to,now); if (deep[now]>L) L=deep[now],side=now; dis[now]=max(dis[now],deep[now]); } inline void GetL() { L=0,deep[side=1]=0; DFS(side,0); L=0,deep[side]=0; DFS(side,0); L=0,deep[side]=0; DFS(side,0); } int q1[MAXN],q2[MAXN],l1=1,r1,l2=1,r2,ans; int main() { N=read(),M=read(); for (int i=2,fa,di; i<=N; i++) fa=read(),di=read(),InsertEdge(fa,i,di); GetL(); // for (int i=1; i<=N; i++) printf("%d ",dis[i]); puts(""); //q1单调减q2单调增 int l=1,r=1; for (; l<=N && r<=N; r++) { while (l1<=r1 && dis[q1[r1]]<=dis[r]) r1--; q1[++r1]=r; while (l2<=r2 && dis[q2[r2]]>=dis[r]) r2--; q2[++r2]=r; if (dis[q1[l1]]-dis[q2[l2]]>M) { ans=max(ans,r-l); while (dis[q1[l1]]-dis[q2[l2]]>M) { l=min(q1[l1],q2[l2])+1; while (l1<=r1 && q1[l1]<l) l1++; while (l2<=r2 && q2[l2]<l) l2++; } } } ans=max(ans,r-l); printf("%d\n",ans); return 0; }
HDU4003
这道题最初自己没想到,可以转化成分组背包来进行dp
$dp[i][j]$表示第$i$个节点用$j$个机器人去遍历子树的方案数,特殊的,$dp[i][0]$表示从之前节点放一个下来遍历完再返回的答案。
然后就可以用类似分组背包的方式dp,非常巧妙。
对于每个根节点$root$,有个容量为$K$的背包;如果它有$i$个儿子,那么就有$i$组物品,价值分别为$dp[son][0],dp[son][1].....dp[son][k]$ ,这些物品的重量分别为$0,1,.....k$
现在要求从每组里选一个物品(且必须选一个物品)装进$root$的背包,使得容量不超过$k$的情况下价值最大。
那么这就是个分组背包的问题了。
但是这里有一个问题,就是每组必须选一个物品。
对于这个的处理,我们先将$dp[son][0]$放进背包,如果该组里有更好的选择,那么就会换掉这个物品,否则的话这个物品就是最好的选择。这样保证每组必定选了一个。
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 10010 int N,S,K; struct EdgeNode{int next,to,dis;}edge[MAXN<<1]; int head[MAXN],cnt=1; inline void AddEdge(int u,int v,int w) {cnt++; edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].dis=w;} inline void InsertEdge(int u,int v,int w) {AddEdge(u,v,w); AddEdge(v,u,w);} int dp[MAXN][11]; inline void DFS(int now,int last) { for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) { DFS(edge[i].to,now); for (int k=K; k>=0; k--) { dp[now][k]+=dp[edge[i].to][0]+2*edge[i].dis; for (int j=1; j<=k; j++) dp[now][k]=min(dp[now][k],dp[now][k-j]+j*edge[i].dis+dp[edge[i].to][j]); } } } int main() { while (~scanf("%d%d%d",&N,&S,&K)) { cnt=1; memset(head,0,sizeof(head)); for (int i=1,x,y,z; i<=N-1; i++) x=read(),y=read(),z=read(),InsertEdge(x,y,z); memset(dp,0,sizeof(dp)); DFS(S,0); printf("%d\n",dp[S][K]); } return 0; }
POJ3107
求重心,按编号顺序输出所有可能
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 50010 int N; struct EdgeNode{int next,to;}edge[MAXN<<1]; int head[MAXN<<1],cnt=1; inline void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;} inline void InsertEdge(int u,int v) {AddEdge(u,v); AddEdge(v,u);} int size[MAXN],dp[MAXN],root,Sz; inline void DFS(int now,int last) { size[now]=1; for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) { DFS(edge[i].to,now); size[now]+=size[edge[i].to]; dp[now]=max(dp[now],size[edge[i].to]); } dp[now]=max(dp[now],Sz-size[now]); if (dp[now]<dp[root]) root=now; } int main() { N=read(); for (int i=1,x,y; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y); dp[root=0]=Sz=N; DFS(1,0); for (int i=1; i<=N; i++) if (dp[i]==dp[root]) printf("%d ",i); return 0; }
POJ3140
傻逼题,直接统计答案然后暴力
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; #define LL long long inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 100010 int N,M,T; struct edgeNode{int u,v;}e[MAXN]; struct EdgeNode{int next,to;}edge[MAXN<<1]; int head[MAXN],cnt=1; inline void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;} inline void InsertEdge(int u,int v) {AddEdge(u,v); AddEdge(v,u);} LL sum[MAXN],Sum,ans; int deep[MAXN],a[MAXN]; inline void DFS(int now,int last) { sum[now]=a[now]; for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) { deep[edge[i].to]=deep[now]+1; DFS(edge[i].to,now); sum[now]+=sum[edge[i].to]; } } inline LL ABS(LL x) {return x>0? x:-x;} int main() { while (N=read(),M=read()) { if (!N && !M) break; cnt=1; memset(head,0,sizeof(head)); Sum=0; memset(sum,0,sizeof(sum)); for (int i=1; i<=N; i++) a[i]=read(),Sum+=a[i]; for (int i=1; i<=M; i++) e[i].u=read(),e[i].v=read(),InsertEdge(e[i].u,e[i].v); DFS(1,0); ans=(1LL<<60); for (int i=1; i<=M; i++) { if (deep[e[i].v]>deep[e[i].u]) swap(e[i].u,e[i].v); ans=min(ans,ABS(sum[e[i].u]-(Sum-sum[e[i].u]) ) ); } printf("Case %d: %I64d\n",++T,ans); } return 0; }
HDU2476
这题又看了题解,自己想到的dp和标算有些差池,但是个人感觉没问题...
先说一下自己的想法,$dp[l][r]$表示区间$[l,r]$的s1和s2匹配的最小代价,这样可以预处理出$dp[i][i]$,然后dp,转移分两种情况,一种是$s2[l]=s2[r]$这样这一段可以直接先同时覆盖,可以从$dp[l+1][r-1]$转移过来,另一种就是枚举断点常规转移,这样再加一点讨论就可以,小数据对拍是没问题的,但是会wa。
正解和我的想法很类似,不过我是直接从s1->s2,而正解是先计算空串到s2的代价,然后再利用其计算s1->s2的代价。
$dp[l][r]$表示从空串的$[l,r]$区间匹配s2的最小代价,也是需要先预处理$dp[i][i]$,然后转移同理,不过稍有不同,$$dp[l][r]=min(dp[l+1][r]+1,dp[l+1][k]+dp[k+1][r](s2[l]=s2[k]) \quad )$$
然后再利用$ans[i]$表示s1->s2的最小代价,转移类似,然后答案就是$ans[N]$
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> using namespace std; char s1[110],s2[110]; int N,dp[110][110],ans[110]; int main() { while (~scanf("%s%s",s1+1,s2+1)) { N=strlen(s1+1); memset(dp,0,sizeof(dp)); for (int i=1; i<=N; i++) dp[i][i]=1; for (int len=2; len<=N; len++) for (int l=1,r=l+len-1; l+len-1<=N; l++,r++) { dp[l][r]=dp[l+1][r]+1; for (int k=l+1; k<=r; k++) if (s2[l]==s2[k]) dp[l][r]=min(dp[l][r],dp[l+1][k]+dp[k+1][r]); // printf("%d %d %d\n",l,r,dp[l][r]); } memset(ans,0,sizeof(ans)); ans[1]=s1[1]==s2[1]? 0:1; for (int i=2; i<=N; i++) { ans[i]=dp[1][i]; if (s1[i]==s2[i]) ans[i]=min(ans[i],ans[i-1]); for (int j=1; j<=i-1; j++) ans[i]=min(ans[i],ans[j]+dp[j+1][i]); } printf("%d\n",ans[N]); } return 0; }
HDU5438
先拓扑一下,把叶节点标记,然后再用并查集维护一下每个块即可。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<queue> using namespace std; #define LL long long inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 100010 int T,N,M,a[MAXN],d[MAXN]; struct EdgeNode{int next,to;}edge[MAXN<<1]; int head[MAXN],cnt=1; inline void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;} inline void InsertEdge(int u,int v) {AddEdge(u,v); AddEdge(v,u);} bool visit[MAXN]; inline void toposort() { queue<int>q; for (int i=1; i<=N; i++) if (d[i]<=1) q.push(i),visit[i]=1; while (!q.empty()) { int now=q.front(); q.pop(); for (int i=head[now]; i; i=edge[i].next) if (--d[edge[i].to]==1) visit[edge[i].to]=1,q.push(edge[i].to); } } int f[MAXN],size[MAXN]; LL sum[MAXN]; inline int F(int x) {if (f[x]==x) return x; return f[x]=F(f[x]);} inline void merge(int x,int y) { int fx=F(x),fy=F(y); if (fx==fy) return; f[fx]=fy; size[fy]+=size[fx]; sum[fy]+=sum[fx]; } int main() { T=read(); while (T--) { cnt=1; memset(head,0,sizeof(head)); N=read(),M=read(); for (int i=1; i<=N; i++) a[i]=read(); memset(d,0,sizeof(d)); for (int i=1,x,y; i<=M; i++) x=read(),y=read(),InsertEdge(x,y),d[x]++,d[y]++; memset(visit,0,sizeof(visit)); toposort(); for (int i=1; i<=N; i++) f[i]=i,sum[i]=a[i],size[i]=1; for (int i=1; i<=N; i++) if (!visit[i]) for (int j=head[i]; j; j=edge[j].next) if (!visit[edge[j].to]) merge(i,edge[j].to); // for (int i=1; i<=N; i++) printf("%d %d %d %d %I64d\n",i,visit[i],F(i),size[F(i)],sum[F(i)]); LL ans=0; for (int i=1; i<=N; i++) if (!visit[i] && F(i)==i && size[i]&1) ans+=sum[i]; printf("%I64d\n",ans); } return 0; }
HDU5293
树形dp,比较巧妙。
$f[x]$表示以$x$为根的子树符合条件的最大价值和,转移需要枚举所有$LCA(u,v)=x$的树链,方程比较显然。$$f[x]=max(f[x],\sum (sum[son]-f[son])+w_{i})$$
其中$sum[x]$表示以$\sum f[son]$,转移很显然是去除$u-->v$路径上的点后所有$deep$最小的子树的和,引用Claris的图:
所以现在要快速的求出绿色部分的和,这个可以利用线段树/树状数组维护dfs序做到$logN$,然后就可以了。
总的时间复杂度$O((N+M)logN)$
多组数据要注意清空,自己忘记清空father导致多WA了很多次,要注意。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<vector> using namespace std; #define LL long long inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 100010 int T,N,M; struct EdgeNode{int next,to;}edge[MAXN<<1]; int head[MAXN],cnt=1; inline void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;} inline void InsertEdge(int u,int v) {AddEdge(u,v); AddEdge(v,u);} struct LineNode{int u,v,w;}l[MAXN]; vector <int> v[MAXN]; int pl[MAXN],dfn,pr[MAXN],deep[MAXN],father[MAXN][21]; inline void DFS(int now,int last) { pl[now]=++dfn; for (int i=1; i<=20; i++) if (deep[now]>=(1<<i)) father[now][i]=father[father[now][i-1]][i-1]; else break; for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) deep[edge[i].to]=deep[now]+1, father[edge[i].to][0]=now, DFS(edge[i].to,now); pr[now]=++dfn; } inline int LCA(int x,int y) { if (deep[x]<deep[y]) swap(x,y); int dd=deep[x]-deep[y]; for (int i=0; i<=20; i++) if (dd&(1<<i)) x=father[x][i]; for (int i=20; i>=0; i--) if (father[x][i]!=father[y][i]) x=father[x][i],y=father[y][i]; return x==y? x:father[x][0]; } LL tree[MAXN<<1]; inline int lowbit(int x) {return x&-x;} inline void Modify(int p,int d) {for (int i=p; i<=dfn; i+=lowbit(i)) tree[i]+=d;} inline LL Query(int p) {LL re=0; for (int i=p; i; i-=lowbit(i)) re+=tree[i]; return re;} LL f[MAXN],g[MAXN]; inline void DP(int now,int last) { for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) DP(edge[i].to,now), g[now]+=f[edge[i].to]; f[now]=g[now]; for (int i=0,x; i<v[now].size(); i++) x=v[now][i], f[now]=max(f[now],g[now]+Query(pl[l[x].u])+Query(pl[l[x].v])+l[x].w); Modify(pl[now],g[now]-f[now]); Modify(pr[now],f[now]-g[now]); } int main() { T=read(); while (T--) { N=read(),M=read(); cnt=1; memset(head,0,sizeof(head)); for (int i=1,x,y; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y); dfn=0; memset(father,0,sizeof(father)); DFS(1,0); for (int i=1; i<=N; i++) v[i].clear(); for (int i=1; i<=M; i++) l[i].u=read(),l[i].v=read(),l[i].w=read(),v[LCA(l[i].u,l[i].v)].push_back(i); memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); memset(tree,0,sizeof(tree)); DP(1,0); printf("%lld\n",f[1]); } return 0; }
HDU5290
这个树形dp有点厉害...自己简单说一下,详细题解
两个状态$f[x][k]$表示以$x$为根的子树全部被摧毁还能从$x$节点向上破坏距离$<=k$的点,$g[x][k]$表示以$x$为根的子树内最深的未被破坏的点距离$x$为$k$;
然后利用这两个状态进行转移;
先考虑$x$节点不被选择的转移:
因为$f[x][k]$可以从$x$再向外扩展$k$的距离,所以$f[x][k]$的值可以从至多$g[x][k]$的值转移得到,并且$f[x][k]$成立的条件一定满足存在$f[son][k+1]$成立,所以转移就是:
$$f[x][k]=min(f[x][k],f[son][k+1]+\sum min(f[son'][0..k+1],g[son'][0..k]))$$
所以这种情况下$g[x][k]$转移同理,必须满足存在$g[son][k-1]$,且其余子节点$f[son][k]$不存在,然后就可以得到转移:
$$g[x][k]=min(g[x][k],g[son][k-1]+\sum min(f[son'][0..k],g[son'][0..k-1]))$$
上述两个转移$son$和$son'$都是$x$的子节点,但$son$指的是哪个必须满足的节点,$son'$是指除此之外的其他节点。
再考虑$x$节点被选择的转移:
比上述显然$$f[x][w[x]]=1+\sum min(f[son][0..w[x]],g[son][0..w[x]-1])$$
这样直接dp的复杂度是$O(NM^{2})$的,可以通过处理前缀最小得到$O(NM)$,优化内存可以通过维护$f,g$的单调性。
No Code
HDU5760
这个区间dp也是比较厉害啊。
要求最长的长度和方案数,很容易想到$O(N^{3})$的dp,但是并不过,那么需要优化这个dp
预处理出$pre[i][j],suf[i][j]$分别表示从$i$这个位置开始它前面的/后面的第一个为$j$的位置。
然后$dp[l][r]$表示$[l,r]$之间的且$a[l]==a[r]$的最长合法回文,$cnt[l][r]$表示合法回文方案数,这时候考虑,$dp[l][r]<=dp[l,r+1...N]$,当区间左端点被固定时,右端点增大,最长合法回文必定不减。
这样就可以简化$N^{3}$的方法中的一维枚举,复杂度降到$O(N^{2})$
这样做的话,由于答案是通过固定左端点,枚举右端点得到,为了保证得到全部区间,左端点从后往前枚举即可。
具体的就是,先初始化$dp[i][i]=cnt[i][i]=1$,然后倒叙枚举左端点,左端点固定枚举右端点;
先用$len$记录当前的最长合法回文长度,$tot$记录当前的合法回文的方案数,然后转移;
当$a[l]==a[r]$时,显然$dp[l][r]=len+2,cnt[l][r]=tot$
当$a[l]>=a[r]$时,就可能需要更新$len$和$tot$的值,令$x=suf[l][a[r]],y=pre[r][a[r]]$,用区间$[x,r]$的值去和当前$len$比较,更新。但是这里的方案数可能会出现部分重复计算的情况,所以应该特判并减去。
具体来说就是如果$dp[x][y]==dp[x][r]$就表示这两个区间方案是相同的,但是又知道$y<r$ 所以,这样就说明$cnt[x][r]$中已经统计过$cnt[x][y]$了,这样$cnt[x][y]$就是重复的部分,就应该减去。
最后枚举$a_{i}$统计答案即可。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 5050 #define P 1000000007 int N,dp[MAXN][MAXN],cnt[MAXN][MAXN],a[MAXN],ls[MAXN],tp,top; int pre[MAXN][MAXN],suf[MAXN][MAXN]; int main() { while (~scanf("%d",&N)) { tp=0,top=0; for (int i=1; i<=N; i++) a[i]=read(),ls[++tp]=a[i]; stable_sort(ls+1,ls+tp+1); for (int i=1; i<=tp; i++) if (ls[top]!=ls[i]) ls[++top]=ls[i]; for (int i=1; i<=N; i++) a[i]=lower_bound(ls+1,ls+top+1,a[i])-ls; memset(pre,0,sizeof(pre)); memset(suf,0,sizeof(suf)); for (int i=1; i<=N+1; i++) for (int j=1; j<=top; j++) if (a[i-1]!=j) pre[i][j]=pre[i-1][j]; else pre[i][j]=i-1; for (int i=N; i>=0; i--) for (int j=1; j<=top; j++) if (a[i+1]!=j) suf[i][j]=suf[i+1][j]; else suf[i][j]=i+1; memset(dp,0,sizeof(dp)); memset(cnt,0,sizeof(cnt)); for (int i=1; i<=N; i++) dp[i][i]=1,cnt[i][i]=1; for (int l=N; l; l--) for (int r=l+1,len=0,tot=1; r<=N; r++) { // printf("<%d %d>\n",l,r); if (a[l]==a[r]) dp[l][r]=len+2,cnt[l][r]=tot; if (a[l]>=a[r]) { int x=suf[l][a[r]],y=pre[r][a[r]]; if (dp[x][r]>len) len=dp[x][r],tot=cnt[x][r]; else if (dp[x][r]==len) { if (y>=x && dp[x][y]==dp[x][r]) tot=(tot-cnt[x][y]+P)%P; tot=(tot+cnt[x][r])%P; } } } int len=0,tot=0; for (int i=1; i<=top; i++) { int l=suf[0][i],r=pre[N+1][i]; if (!l || !r) continue; if (len<dp[l][r]) len=dp[l][r],tot=cnt[l][r]; else if (len==dp[l][r]) tot=(tot+cnt[l][r])%P; } printf("%d %d\n",len,tot); } return 0; }
HDU4044
这题的题意有点难懂,实际上就是考虑类似保卫萝卜树上版本,然后就可以想出大概了。
和背包模型非常接近,所以考虑用背包求解,但是又不能直接利用背包。
首先对于一个节点$i$,我们可以算出给他$j$的费用,可以得到的最大的power,但是实际发现,对于一个节点$i$,计算这个的过程是分为两部分的:
1.计算以这个节点为根的子树中的“最大”power 2.计算这个节点单独考虑时的最大power
因为破坏可能会下放到任意叶子节点,所以每个分支的答案需要尽可能大,而又相互独立,所以第一部分的计算实际上是找子树的最小的最大。
而这两个部分都可以利用分组背包求解,然后就可以了。
#include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<algorithm> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 5010 int T,N,M,sz[MAXN],co[MAXN][55],po[MAXN][55]; struct EdgeNode{int next,to;}edge[MAXN<<1]; int head[MAXN],cnt=1; inline void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; edge[cnt].to=v; head[u]=cnt;} inline void InsertEdge(int u,int v) {AddEdge(u,v); AddEdge(v,u);} int dp[MAXN][505],g[MAXN]; inline void DFS(int now,int last) { bool leaf=0; for (int i=head[now]; i && !leaf; i=edge[i].next) if (edge[i].to!=last) leaf=1; if (leaf) { memset(dp[now],63,sizeof(dp[now])); for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) { DFS(edge[i].to,now); for (int j=M,k,t=0; j>=0; j--) { for (t=0,k=0; k<=j; k++) t=max(t,min(dp[edge[i].to][k],dp[now][j-k])); dp[now][j]=t; } } } for (int j=M; j>=0; j--) g[j]=dp[now][j]; for (int j=M; j>=0; j--) { for (int i=1; i<=sz[now]; i++) if (j>=co[now][i]) dp[now][j]=max(dp[now][j],g[j-co[now][i]]+po[now][i]); g[j]=dp[now][j]; } } int main() { T=read(); while (T--) { N=read(); memset(head,0,sizeof(head)); cnt=1; for (int i=1,x,y; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y); M=read(); for (int i=1,j; i<=N; i++) for (sz[i]=read(),j=1; j<=sz[i]; j++) co[i][j]=read(),po[i][j]=read(); memset(dp,0,sizeof(dp)); DFS(1,0); printf("%d\n",dp[1][M]); } return 0; }