zoukankan      html  css  js  c++  java
  • [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
  • 相关阅读:
    2020.10.23 19级training 补题报告
    2020.10.17 天梯赛练习 补题报告
    2020.10.16 19级training 补题报告
    2020.10.9 19级training 补题报告
    2020.10.10 天梯赛练习 补题报告
    2020.10.3 天梯赛练习 补题报告
    2020.10.2 19级training 补题报告
    第十届山东省ACM省赛复现补题报告
    VVDI Key Tool Plus Adds VW Passat 2015 Key via OBD
    Xhorse VVDI Prog Software V5.0.3 Adds Many MCUs
  • 原文地址:https://www.cnblogs.com/DaD3zZ-Beyonder/p/6045564.html
Copyright © 2011-2022 走看看