[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
",i,j,f[i][j]);
            printf("%d
",g[1]);
        }
    return 0;
}
POJ2152

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
",l,r,dp[l][r]);
        }
    printf("%d
",dp[1][M]);
    return 0;
}
POJ2152

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
",h[x]); break;
                }
        }
    return 0;
}
POJ1988

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
",ans[i]);
    return 0;
}
POJ1984

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
",dp[1][N]); 
        }
    return 0;
}
HDU5900

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
",ans);
    return 0;
}
POJ3162

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
",dp[S][K]);
        }
    return 0;
}
HDU4003

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;
}
POJ3107

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
",++T,ans);
        }
    return 0;
}
POJ3140

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
",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
",ans[N]);
        }
    return 0;
}
HDU2476

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
",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
",ans);
        }
    return 0;
}
HDU5438

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
",f[1]);
        }
    return 0;
}
HDU5293

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>
",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
",len,tot);
        }
    return 0;
}
HDU5760

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
",dp[1][M]);
        }
    return 0;
}
HDU4044
原文地址:https://www.cnblogs.com/DaD3zZ-Beyonder/p/6045564.html