• Codeforces 刷水记录


    Codeforces-566F

    题目大意:给出一个有序数列a,这个数列中每两个数,如果满足一个数能整除另一个数,则这两个数中间是有一条边的,现在有这样的图,求最大联通子图。

    题解:并不需要把图搞出来,可以直接dp,$f[i]$表示以第$i$个数为开头的最大的联通子图,转移类似最长上升子序列,$f[i]=max(f[i],f[j]+1)$

    Code:

    #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 1000010
    int N,a[MAXN],dp[MAXN],ans,id[MAXN];
    int main()
    {
        N=read();
        for (int i=1; i<=N; i++) a[i]=read(),id[a[i]]=i;
        for (int i=1; i<=N; i++) dp[i]=1;
        for (int i=1,now=a[i]; i<=N; i++,now=a[i])
            while (now+a[i]<=a[N]) now+=a[i],dp[id[now]]=max(dp[id[now]],dp[i]+1);
        for (int i=1; i<=N; i++) ans=max(ans,dp[i]);
        printf("%d
    ",ans);
        return 0;
    }
    566F

    Codeforces-559C

    题目大意:给出一个棋盘,棋盘上有K个障碍,问从左上角走到右下角的方案数,棋盘大小$10^{5}*10^{5}$,障碍点数目$2000$,只能向右或者向下走。

    题解:这种题,显然有两种做法,数据范围小,显然可以随便dp,当然也可是直接利用组合数计算,这里只能直接利用组合数计算。

    显然从左上角走到点$(x,y)$的方案数是$ extrm{C}^{x-1}_{x-1+y-1}$

    $step[i]$表示从左上角到第$i$个障碍点的步数,显然$$step[i]= extrm{C} ^{x_{i}-1}_{x_{i}-1+y_{i}-1} - sum ^{j}_{x_{j}<=x_{i},y_{j}<=y_{i}}step[j]* extrm{C}^{x_{i}-x_{j}}_{x_{i}-x_{j}+y_{i}-y_{j}}$$

    然后再计算点$(N,M)$的答案即可。

    Code:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    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 P 1000000007
    #define MAXN 2001
    int N,M,K;
    LL step[MAXN],fac[200010],inv[200010];
    inline LL qp(LL x,int y)
    {
        LL re=1;
        for (int i=y; i; i>>=1,x=x*x%P) if (i&1) re=re*x%P;
        return re;
    }
    inline LL Inv(int x) {return qp(x,P-2);}
    struct PointNode{int x,y;}ct[MAXN];
    inline LL C(int n,int m) {return fac[n]*inv[m]%P*inv[n-m]%P;}
    inline bool cmp(PointNode A,PointNode B) {return A.x==B.x? A.y<B.y : A.x<B.x;}
    int main()
    {
        N=read(),M=read(),K=read();
        fac[0]=1; inv[0]=Inv(fac[0]); 
        for (int i=1; i<=N+M; i++) fac[i]=fac[i-1]*i%P,inv[i]=Inv(fac[i]);
        for (int i=1; i<=K; i++) ct[i].x=read(),ct[i].y=read(); 
        sort(ct+1,ct+K+1,cmp);
        for (int i=1; i<=K; i++)
            {
                step[i]=C(ct[i].x-1+ct[i].y-1,ct[i].x-1);
                for (int j=1; j<=K; j++)
                    if (i!=j && ct[j].x<=ct[i].x && ct[j].y<=ct[i].y)
                        step[i]=(step[i]-step[j]*C(ct[i].x-ct[j].x+ct[i].y-ct[j].y,ct[i].x-ct[j].x)%P+P)%P;
            }
        LL ans=C(N-1+M-1,N-1);
        for (int i=1; i<=K; i++) ans=(ans-step[i]*C(N-ct[i].x+M-ct[i].y,N-ct[i].x)%P+P)%P;
        printf("%I64d
    ",ans);
        return 0;
    }
    559C

    Codeforces-545C

    题目大意:给出一些树,每个树有两个信息,x表示树的位置,h表示树的高度,每次砍树,被砍的树会向左右两边其中一边倒下,即覆盖$[x_{i}-h_{i},x_{i}],[x_{i},x_{i}+h_{i}]$,但是如果一棵树倒下会和其他树(倒下或不倒下)发生重合,则不能向那边倒下,问最多砍掉的树的个数。

    题解:开始以为是很标准的二分,于是开始想,二分出一个height把大于height的可以砍得树砍掉,然后看能砍的棵树。然后意识到,这个题完全不用什么二分,可以直接贪心。

    首先第1棵树和最后一棵树显然可以砍到,只要让他们分别往没树的方向倒即可,这样对于2~N-1棵树,可以从左到右贪心,对于一棵树,如果它可以向左倒,那么它一定向左倒,因为这样不会对之后有任何影响;对于一棵树,如果它不能向左倒,那么如果它向右倒不会覆盖右边的没砍的树,那么就让他向右倒下;这样扫一遍得到答案。

    这样显然是正确的,首先能向左倒不向右倒显然,至于第二种方式,可以分情况讨论:

    假如x向右倒不会覆盖到x+1,让x向右倒,这时加入x+1向左倒,不会和倒下的x覆盖,那么让x+1向左倒,这样这两棵树仍对后面无影响,且最优;

    假如x向右倒不会覆盖到x+1,让x向右倒,这时加入x+1向左倒,恰好会覆盖倒下的x,那么x+1不能向左,这样看起来有影响后面的决策,那么假设x不能倒,这样往后转移,最优砍树答案数是不变的,仅仅是砍树的方案发生改变。(自己表述有问题,不过还是比较好想的)

    所以就可以这样贪心的扫一遍得到答案。 同时感觉这题可以dp,一种类似背包的dp。

    Code:

    #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 100010
    int N;
    struct TreeNode{int x,h;}a[MAXN];
    int main()
    {
        N=read(); if (N==1) {puts("1"); return 0;}
        for (int i=1; i<=N; i++) a[i].x=read(),a[i].h=read();
        int ans=2,last=a[1].x;
        for (int i=2; i<=N-1; last=max(a[i].x,last),i++)
            if (a[i].x-a[i].h>last) ans++;
                else if (a[i].x+a[i].h<a[i+1].x) ans++,last=a[i].x+a[i].h;
        printf("%d
    ",ans);
        return 0;
    }
    545C

    Codeforces-543A

    题目大意:$N$个人打$M$行代码,第$i$个人会打一行代码会出错$a_{i}$次,现在询问总错误数$<=B$的方案数。

    题解:比较简单的dp,状态$dp[i][j][k]$表示前$i$个人打了$j$行出错数为$k$的方案数。

    转移可以枚举第$i$个人打多少行得到$$dp[i][j][k]=sum ^{j}_{l=1}dp[i-1][j-l][ k-l*a[i] ]$$

    然而这个转移太愚蠢了,同样的状态可以做到$O(N^{3})$转移。$$dp[i][j][k]=dp[i-1][j][k]+dp[i][j-1][ k-a[i] ]$$

    然后这样内存开不下,把$i$这一维滚动掉就可以了。

    Code:

    #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;
    }
    int N,M,B,P,a[510],dp[2][510][510],ans;
    int main()
    {
        N=read(),M=read(),B=read(),P=read();
        for (int i=1; i<=N; i++) a[i]=read();
        dp[0][0][0]=1;
        for (int i=1; i<=N; i++)
            for (int j=0; j<=M; j++)
                for (int k=0; k<=B; k++)
                    dp[i&1][j][k]=( ( (k-a[i]>=0 && j-1>=0) ? dp[i&1][j-1][k-a[i]]:0) +dp[(i-1)&1][j][k])%P;
        for (int i=0; i<=B; i++) ans=(ans+dp[N&1][M][i])%P;
        printf("%d
    ",ans);
        return 0;
    }
    543A

    Codeforces-540D

    题目大意:现在存在$x$个石头,$y$个剪刀,$z$个包袱,每当两个见面,输的人会淘汰,求最后剩下的为石头、剪刀、包袱的概率分别是多少。

    题解:弱智概率dp,设状态$f[i][j][k]$表示还剩$i$个石头,$j$个剪刀,$k$个包袱的概率。

    显然$f[x][y][z]=1$然后可以逆推出各种状态,$f[i-1][j][k]$表示$i$和$k$见面,这样转移显然就是$f[i-1][j][k]+=frac {i*k}{i*k+i*j+k*j}*f[i][j][k]$其余同理。最后分别统计答案即可。

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    using namespace std;
    int x,y,z;
    double f[101][101][101],rock,scissor,paper;
    int main()
    {
        scanf("%d%d%d",&x,&y,&z);
        f[x][y][z]=1;
        for (int i=x; i>=1; i--)
            for (int j=y; j>=1; j--)
                for (int k=z; k>=1; k--)
                    f[i-1][j][k]+=(double)i*k/(i*j+i*k+k*j)*f[i][j][k],
                    f[i][j-1][k]+=(double)i*j/(i*j+i*k+k*j)*f[i][j][k],
                    f[i][j][k-1]+=(double)k*j/(i*j+i*k+k*j)*f[i][j][k];
        for (int i=1; i<=x; i++)
            for (int j=0; j<=y; j++)
                rock+=f[i][j][0];
        for (int i=1; i<=y; i++)
            for (int j=0; j<=z; j++)
                scissor+=f[0][i][j];
        for (int i=1; i<=x; i++)
            for (int j=0; j<=z; j++)
                paper+=f[i][0][j];
        printf("%.10lf %.10lf %.10lf
    ",rock,scissor,paper);
        return 0;
    }
    540D

    Codeforces-526E

    题目大意:给出一个N个数的环,每次给出一个b,对这个环分段,每段必须相邻,且每段的和$<=b$,求最小段数。

    题解:首先展环为链,然后处理。首先,尽可能的多取,一定比少取要优。定义$st[i]$表示以$i$位置为最后的方案的开始点,$cnt[i]$表示以$i$位置为最后的方案的方案数。

    然后就可以dp..扫一遍环,得到前一段的末尾$j$就可转移$$st[i]=st[j],cnt[i]=cnt[j]+1$$

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    using namespace std;
    #define LL long long
    inline LL read()
    {
        LL 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,Q,a[MAXN<<1],st[MAXN<<1],cnt[MAXN<<1];
    LL sum[MAXN<<1];
    int main()
    {
        N=read(),Q=read();
        for (int i=1; i<=N; i++) a[i]=a[i+N]=read(),st[i]=i;
        for (int i=1; i<=2*N; i++) sum[i]=sum[i-1]+a[i];
        while (Q--)
            {
                LL b=read();
                for (int j=1,i=N+1; i<=N*2; i++)
                    {
                        while (sum[i]-sum[j]>b) j++;
                        st[i]=st[j];
                        cnt[i]=cnt[j]+1;
                        if (i-st[i]>=N) {printf("%d
    ",cnt[i]); break;}
                    }
            }
        return 0;
    }
    526E

    Codeforces-533B

    题目大意:每个人有一个领导,1节点除外,同时每个点有一个权值,从这棵树中取出一些点,使得每个领导领导的人数是偶数,且权值和最大,输出权值和。

    题解:这个题的题意比较绕,注意的题意是:假设U有三个儿子,可以选两个合法的儿子,并且从第三个儿子的子孙里再选偶数个合法子孙。

    真正读懂题之后就比较好考虑怎么做了,分子树选到的儿子孙的奇偶来dp;$dp[x][0/1]$表示以x为根的子树中选了奇数个和偶数个的合法的方案数。

    这样转移就可以奇偶交替进行。$$dp[x][0]=max(dp0+dp[v][0],dp1+dp[v][1])$$ $$dp[x][1]=max(dp[x][0]+a[x],max(dp0+dp[v][1],dp1+dp[v][0]))$$

    其中最后的答案就是$max(dp[1][1],dp[1][0])$

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #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 200010 
    int N,p[MAXN],a[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) {if (u==-1) return; AddEdge(u,v); AddEdge(v,u);}
    LL dp[2][MAXN];
    #define INF 0x7fffffff 
    inline void DFS(int now)
    {
        dp[1][now]=-INF,dp[0][now]=0;
        for (int i=head[now]; i; i=edge[i].next)
            if (edge[i].to!=p[now])
                {
                    DFS(edge[i].to);
                    LL dp1=dp[1][now],dp0=dp[0][now];
                    dp[1][now]=max(dp0+dp[1][edge[i].to],dp1+dp[0][edge[i].to]);
                    dp[0][now]=max(dp0+dp[0][edge[i].to],dp1+dp[1][edge[i].to]);
                }
        dp[1][now]=max(dp[1][now],dp[0][now]+a[now]);
    }
    int main()
    {
        N=read();
        for (int i=1; i<=N; i++) p[i]=read(),a[i]=read(),InsertEdge(p[i],i);
        DFS(1);
        printf("%I64d
    ",max(dp[0][1],dp[1][1]));
        return 0;
    }
    533B

    Codeforces-518D

    题目大意:有N个人,每秒有p的概率有一个人进入电梯,总共有t秒,求期望。

    题解:比较简单的概率dp,设状态$dp[i][j]$表示$i$秒有$j$个人的概率;转移很显然:$$dp[i][j]=p*dp[i-1][j-1]+(1-p)*dp[i-1][j]$$

    但是这少考虑了一种情况,如果$j==n$了之后,$dp[i][j]$会完全继承$dp[i-1][j]$的状态,所以当$j==n$时,转移就是:$$dp[i][j]=p*dp[i-1][j-1]+dp[i-1][j]$$

    答案是期望,那就dp之后再扫一遍求一下即可。

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    using namespace std;
    int n,t;
    double p,dp[2010][2010];
    int main()
    {
        scanf("%d%lf%d",&n,&p,&t);
        dp[0][0]=1.0;
        for (int i=1; i<=t; i++)
            for (int j=0; j<=n; j++)
                {
                    if (j==0) {dp[i][j]=(1.0-p)*dp[i-1][j]; continue;}
                    if (j==n) {dp[i][j]=p*dp[i-1][j-1]+dp[i-1][j]; continue;}
                    dp[i][j]+=p*dp[i-1][j-1]+(1.0-p)*dp[i-1][j];
                }
        double ans=0;
        for (int i=1; i<=n; i++) ans+=dp[t][i]*i;
        printf("%.7lf
    ",ans);
        return 0;
    }
    518D

    Codeforces-527D

    题目大意:有N个点,每个点有两个数值$x_{i}$和$w_{i}$,定义两个点$<u,v>$有边,需要满足$|x_{i}-x_{j}|>=w_{i}+w_{j}$,现在要求找一个点集,使得这个点集中的点两两有边,求最大点数。

    题解:首先考虑两点有边的情况,需要满足$|x_{i}-x_{j}|>=w_{i}+w_{j}$,但是由于边无序性,假设$x_{i}>=x_{j}$,则这就可以化成$x_{i}-x_{j}>=w_{i}+w_{j}$,进一步转化,就可以得到:$$x_{i}-w_{i}>=x_{j}+w_{j}$$

    这样可以先贪心的先对点进行排序,关键字为$x_{i}+w_{i}$。

    考虑dp,dp方程显然$dp[i]$表示以第$i$个点结尾的点集,最大的点数,转移也很显然:$$dp[i]=max(dp[i],dp[j]+1),<i,j>相连$$

    这样对于点$i$,就是找$x_{j}-w_{j}<=x_{i}+w_{j}$的点中,$dp[j]$最大$j$的转移,这显然可以利用线段树优化转移,单次转移复杂度$logN$,总复杂度$O(NlogN)$。

    Code:

    #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 200010
    int N,val[MAXN];
    struct PointNode {int x,w;}p[MAXN];
    inline bool cmp(PointNode A,PointNode B) {return A.x+A.w<B.x+B.w;}
    namespace SegmentTree
    {
        struct {int l,r,maxx;}tree[MAXN<<2];
        #define ls now<<1
        #define rs now<<1|1
        inline void Update(int now) {tree[now].maxx=max(tree[ls].maxx,tree[rs].maxx);}
        inline void Build(int now,int l,int r)
        {
            tree[now].l=l; tree[now].r=r;
            if (l==r) return;
            int mid=(l+r)>>1;
            Build(ls,l,mid); Build(rs,mid+1,r);
        }
        inline void Modify(int now,int pos,int D)
        {
            int l=tree[now].l,r=tree[now].r;
            if (l==r) {tree[now].maxx=D; return;}
            int mid=(l+r)>>1;
            if (pos<=mid) Modify(ls,pos,D); else Modify(rs,pos,D);
            Update(now);
        }
        inline int Query(int now,int L,int R)
        {
            if (R<L) return 0;
            int l=tree[now].l,r=tree[now].r;
            if (L<=l && R>=r) return tree[now].maxx;
            int mid=(l+r)>>1,re=0;
            if (L<=mid) re=max(re,Query(ls,L,R));
            if (R>mid) re=max(re,Query(rs,L,R));
            return re;
        }
    }
    using namespace SegmentTree;
    int main()
    {
        N=read();
        for (int i=1; i<=N; i++) p[i].x=read(),p[i].w=read(),val[i]=p[i].x+p[i].w;
        sort(p+1,p+N+1,cmp); sort(val+1,val+N+1);
        Build(1,1,N);
        for (int i=1,j; i<=N; i++)
            j=upper_bound(val+1,val+N+1,p[i].x-p[i].w)-val -1,
            Modify(1,i,Query(1,1,j)+1);
        printf("%d
    ",tree[1].maxx);
        return 0;
    }
    527D

    Codeforces-525E

    题目大意:给出N个数,K个操作,每个操作可以把一个数变成它的阶乘,取任意多个数,加和后答案恰好为S的方案数。

    题解:这题感觉可以dp,但是并不能表示状态;

    这个题每个数的状态有3种,1.直接选它2.不选它3.把他变成阶乘后选它,所以总状态数就是$3^{N}$,这里$N<=25$,显然空间和时间都不允许。

    一种思想交过Meet-in-the-middle,就是把一个大的问题分成两段来解决,和分治有点像。

    这个题也是一样,把序列分成$[1,frac{N}{2}]和[frac{N}{2}+1,N]$然后两段分别处理。

    对前一段爆搜,把每种状态的答案记录下来,然后再对后一段爆搜,搜到已有状态就可以直接加到答案里。

    这样每次爆搜的复杂度就是$O(3^{frac{N}{2}})$的,就可以通过。

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<map>
    using namespace std;
    #define LL long long
    int N,K,a[30],Mx;
    LL S,fac[30],ans;
    map<LL,int>cnt[30];
    inline void back(int now,int k,LL sum)
    {
        if (sum<0) return;
        if (now==N/2) {cnt[k][sum]++; return;}
        if (a[now]<=Mx && k) back(now-1,k-1,sum-fac[ a[now] ]);//变后再选 
        back(now-1,k,sum-a[now]);//直接选 
        back(now-1,k,sum); //不选 
    }
    inline void front(int now,int k,LL sum)
    {
        if (sum>S) return;
        if (now==N/2+1) {for (int i=k; i<=K; i++) ans+=cnt[i][sum]; return;}
        if (a[now]<=Mx && k<K) front(now+1,k+1,sum+fac[ a[now] ]);
        front(now+1,k,sum+a[now]);
        front(now+1,k,sum);
    }
    int main()
    {
        scanf("%d%d%I64d",&N,&K,&S);
        fac[0]=1; for (int i=1; i<=20 && fac[i-1]<=S; i++) fac[i]=fac[i-1]*i;
        for (int i=1; i<=20; i++) if (fac[i]>S) {Mx=i-1; break;}
        for (int i=1; i<=N; i++) scanf("%d",&a[i]);
        sort(a+1,a+N+1);
        back(N,K,S); front(1,0,0);
        printf("%I64d
    ",ans); 
        return 0;
    }
    525E

    Codeforces-538E

    题目大意:给出一个树,开始时两个人位于根节点,两个人轮流操作,先手第一个人希望达到的点尽可能大,后手第二个人希望达到的点尽可能小,两个人绝顶聪明,有一个操控全局的人,他可以任意指定叶节点的值,求能够到达的最大值和最小值。

    题解:这道题真TM恶心。

    首先可以先考虑一下这种游戏时自己的决策,如果想要达到的最大,那么一定会向一个大数多的,且数值大的子树移动,找最小是相同的。

    设计状态$f[0/1][i]$表示最优情况下最大/最小到$i$点子树中第$f[0/1][i]$大/小的那个子树,然后可以分奇偶转移,$$f[0][i]=min(f[0][i],f[0][j]) (deep为奇数)$$  $$f[0][i]=sum f[0][j] (deep为偶数)$$

    然后就可以dp,初始化$f[0][i]=INF,f[1][i]=0$先手后手两遍dp即可得到答案。

    Code:

    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<cstdio>
    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 200010
    int N;
    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);}
    int f[2][MAXN],tot;
    #define INF 0x7fffffff
    inline void DFS(int now,int last,int opt)
    {
        bool flag=0;
        for (int i=head[now]; i; i=edge[i].next)
            if (edge[i].to!=last)
                {
                    flag=1;
                    DFS(edge[i].to,now,opt^1);
                    if (opt) 
                        f[1][now]+=f[0][edge[i].to];
                    else 
                        f[0][now]=min(f[0][now],f[1][edge[i].to]);
                }            
        if (!flag) f[0][now]=f[1][now]=1,tot++;
    }
    int main()
    {
        N=read();
        for (int i=1,x,y; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y);
        memset(f[0],127,sizeof(f[0])); memset(f[1],0,sizeof(f[1]));
        DFS(1,0,0);
        DFS(1,0,1);
        printf("%d %d
    ",tot/2-f[0][1]+1,f[1][1]);
        return 0;
    }
    538E

    Codeforces-509F

    题目大意:给出一个程序段,求用这个程序段去遍历一棵树,得到给定序列的不同的树的方案数。

    used[1 ... n] = {0, ..., 0};
    procedure dfs(v):
    print v;
    used[v] = 1;
    for i = 1, 2, ..., n:
    if (a[v][i] == 1 and used[i] == 0):
    dfs(i);
    dfs(1);

    题解:这显然就是dfs序,,,但是这里是邻接矩阵存的图,所以一个根的几个儿子一定是从小到大开始输出的。

    这样就可以dp,状态$dp[l][r]$表示$[l,r]$这段可能的树的数量,显然,这样定义状态$l$一定是这一段的根,转移显然是利用乘法原理统计答案:$$dp[l][r]=sum ^{r}_{k=l+1} dp[l+1][k]*dp[k][r] qquad 满足a_{l+1}<a_{i+1}$$

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    using namespace std;
    #define LL long long
    #define P 1000000007
    int N,a[510],f[510][510];
    int main()
    {
        scanf("%d",&N);
        for (int i=1; i<=N; i++) scanf("%d",&a[i]);
        for (int i=1; i<=N; i++) f[i][i]=1;
        for (int len=2,l,r; len<=N; len++)
            for (int i=1; i+len-1<=N; i++)
                {
                    l=i,r=l+len-1;
                    for (int k=l+1; k<=r; k++)
                        if (k==r || a[l+1]<a[k+1])
                            (f[l][r]+=(LL)f[l+1][k]*f[k][r]%P)%=P;
                }
        printf("%d
    ",f[1][N]);
        return 0;
    }
    509F

    Codeforces-573B

    题目大意:给出一行方块组成的塔,每个位置的高度为$h_{i}$,每次操作可以将所有暴露在外面的方块消掉,暴露在外面指的这个方块有四联通的一边是空,问最少需要消多少次能消完。

    题解:找一找规律就很好做了;首先每次消暴露在外面,这样,显然每一个位置的塔至少最上端一定会被消去,最左最右一定全部被消去,然后每个中间的位置可能会被消去更多的,当且当它的左右有比他低的。

    这就很好做了,所以第一次的变换就可以写出来:$$h[i]-->h'[i]=min(h[i-1],min(h[i+1],h[i]-1))$$同理,能得到多次操作后的$h'[i]$的值:$$h[i]-->h'[i]=min^{i}_{j=1}(h[i-j+1]-(k-j),h[i+j-1]-(k-j)) qquad k表示轮数$$

    然后这个东西随便搞搞就可以出来了,前后扫一遍。

    (不想自己翻译题目,于是找别人的题目大意,然后看见别人标题是线段树+dp,就顺手写了个线段树,然后发现这弱智东西扫一遍就可以得到了吧!)

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    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
    #define INF (1LL<<60)
    int N,h[MAXN];
    LL ans,pre[MAXN],suf[MAXN];
    namespace SegmentTree
    {
        struct SgtTreeNode{int l,r; LL tag,minx;}tree[MAXN<<2];
        #define ls now<<1
        #define rs now<<1|1
        inline void Update(int now) {tree[now].minx=min(tree[ls].minx,tree[rs].minx);}
        inline void Build(int now,int l,int r)
        {
            tree[now].l=l,tree[now].r=r; tree[now].tag=0;
            if (l==r) {tree[now].minx=h[l]; return;}
            int mid=(l+r)>>1;
            Build(ls,l,mid); Build(rs,mid+1,r);
            Update(now);
        }
        inline void PushDown(int now)
        {
            if (tree[now].l==tree[now].r || !tree[now].tag) return;
            LL delta=tree[now].tag; tree[now].tag=0;
            tree[ls].tag+=delta; tree[rs].tag+=delta;
            tree[ls].minx+=delta; tree[rs].minx+=delta;
        }
        inline void Modify(int now,int L,int R)
        {
            PushDown(now);
            int l=tree[now].l,r=tree[now].r;
            if (L<=l && R>=r) {tree[now].tag+=1; tree[now].minx+=1; return;}
            int mid=(l+r)>>1;
            if (L<=mid) Modify(ls,L,R);
            if (R>mid) Modify(rs,L,R);
            Update(now);
        }
        inline LL Query(int now,int L,int R)
        {
            PushDown(now);
            int l=tree[now].l,r=tree[now].r;
            if (L<=l && R>=r) return tree[now].minx;
            int mid=(l+r)>>1; LL re=INF;
            if (L<=mid) re=min(re,Query(ls,L,R));
            if (R>mid) re=min(re,Query(rs,L,R));
            return re;
        }
    }
    int main()
    {
        N=read();
        for (int i=1; i<=N; i++) h[i]=read();
        SegmentTree::Build(1,0,N+1);
        for (int i=1; i<=N; i++) SegmentTree::Modify(1,0,i-1),pre[i]=SegmentTree::Query(1,0,i);
        SegmentTree::Build(1,0,N+1);
        for (int i=N; i>=1; i--) SegmentTree::Modify(1,i+1,N+1),suf[i]=SegmentTree::Query(1,i,N+1);
    //    for (int i=1; i<=N; i++) printf("%d  %d
    ",pre[i],suf[i]);
        for (int i=1; i<=N; i++) ans=max(ans,min(pre[i],suf[i]));
        printf("%I64d
    ",ans);
        return 0;
    }
    573B

    Codeforces-118D

    题目大意:有两种不同的兵种,兵种1有N个单位,兵种2有M中单位,现在将这些兵排成一行,要求兵种1最多连续不超过K1个,兵种2最多连续不超过K2个。

    题解:显然可以dp,状态$dp[i][j][0/1]$表示兵种1有$i$个,兵种2有$j$个,最后一个是兵种1/兵种2的符合条件的方案数,然后转移就是瞎枚举$$dp[i][j][0]=sum ^{min(K1,i)}_{i'=1} dp[i-i'][j][1]$$ $$dp[i][j][1]=sum ^{min(K2,j)}_{j'=1} dp[i][j-j'][0]$$

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    using namespace std;
    #define P 100000000
    int N,M,K1,K2,dp[101][101][2];
    int main()
    {
        scanf("%d%d%d%d",&N,&M,&K1,&K2);
        for (int i=1; i<=K1; i++) dp[i][0][0]=1;
        for (int i=1; i<=K2; i++) dp[0][i][1]=1;
        for (int i=1; i<=N; i++)
            for (int j=1; j<=M; j++)
                {
                    for (int k1=1; k1<=K1 && k1<=i; k1++) (dp[i][j][0]+=dp[i-k1][j][1])%=P;
                    for (int k2=1; k2<=K2 && k2<=j; k2++) (dp[i][j][1]+=dp[i][j-k2][0])%=P;
                }
        printf("%d
    ",(dp[N][M][0]+dp[N][M][1])%P);
        return 0;
    }
    118D

    Codeforces-4D

    题目大意:给出N个物品,每个物品有w,h两种权值,给出一个初始的w,h,求用给定的N个物品组成 最长的 以给出的初始为开头的 严格上升的序列,输出一种可行解。 物品的比较满足二维偏序。

    题解:二维偏序显然可以排序乱搞...先排序,然后dp; $$dp[i]=max(dp[j]+1,dp[i]) qquad a_{j}<a_{i}$$ 输出可行解,就在转移的时候记录从哪一个转移过来,最后倒叙统计一下并输出即可。

    Code:

    #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 5001
    int N,w,h,dp[MAXN],ans,from[MAXN],Ans[MAXN],top;
    struct Node{int w,h,id;}a[MAXN];
    inline bool cmp(Node A,Node B) {return A.w==B.w? A.h<B.h:A.w<B.w;}
    int main()
    {
        N=read(); w=read(); h=read();
        for (int i=1; i<=N; i++) a[i].w=read(),a[i].h=read(),a[i].id=i;
        sort(a+1,a+N+1,cmp);
        for (int i=1; i<=N; i++)
            if (a[i].w>w && a[i].h>h)
                {
                    for (int j=0; j<=i-1; j++)
                        if (a[j].w<a[i].w && a[j].h<a[i].h)
                            from[i]=(dp[i]<dp[j]+1)? j:from[i],
                            dp[i]=max(dp[j]+1,dp[i]);
                }
        for (int i=1; i<=N; i++) ans=max(dp[i],ans);
        for (int i=1; i<=N && !top; i++) 
            if (dp[i]==ans)
                for (int now=i; now; now=from[now]) Ans[++top]=a[now].id;
        printf("%d
    ",ans); 
        if (!ans) return 0;
        for (int i=top; i>=1; i--) printf("%d ",Ans[i]); puts("");
        return 0;
    }
    4D

    Codeforces-161D

    题目大意:给出一个树,树上边的长度为1,求有多少无序点对,满足距离恰好为K。

    题解:可以直接树形dp得到,设状态$dp[x][k]$表示到第$x$个节点距离恰好为$k$的点数,转移很显然$$dp[x][k]=sum _{y=son} dp[y][k-1]$$ 可以在dp的过程中统计答案,注意要开longlong

    Code:

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<cstdio>
    #include<cmath>
    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 50010
    int N,K;
    LL ans,dp[MAXN][501];
    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);}
    inline void DFS(int now,int last)
    {
        dp[now][0]=1;
        for (int i=head[now]; i; i=edge[i].next)
            if (edge[i].to!=last)
                {
                    DFS(edge[i].to,now);
                    for (int k=0; k<=K-1; k++) ans+=(LL)dp[now][K-k-1]*dp[edge[i].to][k];
                    for (int k=1; k<=K; k++) dp[now][k]+=dp[edge[i].to][k-1];
                }
    }
    int main()
    {
        N=read(),K=read();
        for (int i=1,x,y; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y);
        DFS(1,0);
        printf("%I64d
    ",ans);
        return 0;
    }
    161D

    Codeforces-429B

    题目大意:给出一个网格,第一个人从$(1,1)-->(N,M)$第二个人从$(N,1)-->(1,M)$,两个人路径上经过一次的点可以得到这个点的权值,被两个人都经过的点将无法得到这个点的权值,求最大权值。

    题解:因为两个人都经过的点无法得到权值,且权值$>=0$,而且两个人不可避免会相交,所以显然越少相交越好,所以可以贪心的枚举哪个点相交。

    剩下的答案可以dp得到,$dp[0/1/2/3][i][j]$分别表示从$(1,1)-->(N,M) / (N,M)-->(1,1) / (N,1)-->(1,M) / (1,M)-->(N,1)$的最大权值,然后这个瞎搞就搞出来了。

    然后暴力枚举每个内层点为断点就可以了,枚举的时候讨论一下即可。

    Code:

    #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 1010
    int N,M,a[MAXN][MAXN],dp[4][MAXN][MAXN],ans;
    int main()
    {
        N=read(),M=read();
        for (int i=1; i<=N; i++)
            for (int j=1; j<=M; j++) a[i][j]=read();
        for (int i=1; i<=N; i++)
            for (int j=1; j<=M; j++)
                dp[0][i][j]=max(dp[0][i-1][j],dp[0][i][j-1])+a[i][j];
        for (int i=N; i>=1; i--)
            for (int j=M; j>=1; j--)
                dp[1][i][j]=max(dp[1][i+1][j],dp[1][i][j+1])+a[i][j];
        for (int i=N; i>=1; i--)
            for (int j=1; j<=M; j++)
                dp[2][i][j]=max(dp[2][i+1][j],dp[2][i][j-1])+a[i][j];
        for (int i=1; i<=N; i++)
            for (int j=M; j>=1; j--)
                dp[3][i][j]=max(dp[3][i-1][j],dp[3][i][j+1])+a[i][j];
                
    //    puts("");
    //    for (int k=0; k<=3; k++,puts("================"))
    //        for (int i=1; i<=N; i++,puts(""))
    //            for (int j=1; j<=M; j++)
    //                printf("%d  ",dp[k][i][j]);
        
        for (int i=2; i<=N-1; i++)
            for (int j=2; j<=M-1; j++)
                ans=max(ans , max( dp[0][i-1][j]+dp[1][i+1][j]+dp[2][i][j-1]+dp[3][i][j+1],
                                   dp[0][i][j-1]+dp[1][i][j+1]+dp[2][i+1][j]+dp[3][i-1][j]) );
        printf("%d
    ",ans);
        return 0;
    }
    429B

    Codeforces-607B

    题目大意:给出一个序列,每次可以消去一段连续的回文,消去之后两边合并,求最少消几次能全部消完。

    题解:区间dp好题!

    首先状态表示为$dp[l][r]$表示消去$[l,r]$这段的最小步数,这很显然,然后正常枚举断点转移$dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r])$也很显然

    难点在于处理消回文,开始想错了,$O(N^3)$的复杂度感觉可以再枚举一下跟区间的开头一样的位置,判断是否可以当回文删掉,但这样并不科学,因为就算找到也没法科学地判断是否是回文,而且最难搞的是没法处理删掉这段回文之后,其余的两段合并起来,正解是比较巧妙的方法。

    首先,先假设已经在处理$dp[l][r]$的时候,它的所有小的部分已经搞定了,那么假如$a_{l}==a_{r}$则这两个可以当做一个回文的最左最右边,和回文一起删掉,而这样的答案就是$dp[l+1][r-1]$,而且区间$[l+1,r-1]$消除到最后,一定会剩下一个回文串(最差剩下一个单一的),而回文串两边各加上相同的,显然还是个回文,所以可以一并删掉,所以回文的转移就得到了:$$dp[l][r]=min(dp[l][r],dp[l+1][r-1]) qquad (a_{l}==a_{r} 且 l+1<=r-1)$$

    这样就在$O(N^3)$的时间复杂度下解决问题了。

    Code:

    #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 510
    int N,a[MAXN],dp[MAXN][MAXN];
    int main()
    {
        N=read();
        for (int i=1; i<=N; i++) a[i]=read();
        memset(dp,127,sizeof(dp));
        for (int i=1; i<=N; i++) dp[i][i]=1;
        for (int len=2; len<=N; len++)
            for (int i=1; i+len-1<=N; i++)
                {
                    int l=i,r=l+len-1;
                    for (int k=l; k<=r; k++)
                        dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]);
                    if (a[l]==a[r]) dp[l][r]=min(dp[l][r], l+1>r-1? 1:dp[l+1][r-1]);
                }
        printf("%d
    ",dp[1][N]);
        return 0;
    }
    607B

    Codeforces-149D

    题目大意:给出一个括号序列,每个括号可以上色或不上,上色又有两种不同的颜色可以上,但必须满足要求:1.相互匹配的一对括号,有且只有一个能上色2.相邻两个括号不能同时同上一种颜色,但可以同时不上色。问合法方案数。

    题解:这个题非常好!很显然可以区间dp,但是又比较麻烦..$f[l][r][x][y]$表示$[l,r]$段合法的方案数,$x$表示$l$位置的上色方案为$x$,$y$表示$r$位置的上色方案为$y$,其中$0$表示无色,$1/2$分别代表两种不同的颜色

    然后这样就可以区间dp了,然后转移很能搞事,需要讨论很多种情况。

    如果$l和r的括号恰好匹配$那么,转移可以$$f[l][r][x][y]=sum f[l+1][r-1][x'][y'] qquad (l和l+1满足条件2,且r-1和r满足条件2)$$

    如果不匹配,那就需要拆区间,假设$pa[l]$表示与$l$匹配的括号位置,那么转移就是$$f[l][r][x][y]=sum {f[l][pa[l]][x][x']*f[pa[l]+1][r][y'][y]} qquad (x'和y'符合条件2)$$

    这里$l$和$pa[l]$的限制就放到递归中限制了。

    这道题写递推简直蛋疼,所以写的记搜,速度还是很可观的。

    Code:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    #define LL long long
    #define P 1000000007
    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
    char s[MAXN];
    int N,pa[MAXN],st[MAXN],tp,f[MAXN][MAXN][3][3],ans;
    inline int dp(int l,int r,int x,int y)
    {
    //    printf("<%d %d %d %d> :: ",l,r,x,y);
        if (~f[l][r][x][y]) return f[l][r][x][y];
        if (l>r) return 0;
        if (r-l+1==2) 
            if ((x && !y) || (!x && y)) return f[l][r][x][y]=1;
                else return f[l][r][x][y]=0;
        //这里是一个特判相邻的两个正好匹配,显然情况就4种且方案数都为1 
        LL tmp=0;
        if (pa[l]==r)//这里是处理l,r正好匹配的时候 
            if ((x && !y) || (!x && y))//这是在保证匹配的时候xy颜色合法 
                for (int xx=0; xx<=2; xx++)
                    for (int yy=0; yy<=2; yy++)
                        if (!(x && xx && xx==x) && !(y && yy && yy==y)) 
                            tmp=(tmp+dp(l+1,r-1,xx,yy))%P;
                        else;
            else return f[l][r][x][y]=0;
        else //这里是处理l,r不匹配的时候,区间断开统计的情况 
            for (int xx=0; xx<=2; xx++)
                for (int yy=0; yy<=2; yy++)
                    if (!(xx==yy && xx)) 
                        tmp=(tmp+(LL)dp(l,pa[l],x,xx)*dp(pa[l]+1,r,yy,y)%P)%P;
        return f[l][r][x][y]=tmp;
    }
    int main()
    {
        scanf("%s",s+1); N=strlen(s+1);
        for (int i=1; i<=N; i++)
            if (s[i]=='(') st[++tp]=i;
                else pa[i]=st[tp--];
        for (int i=1; i<=N; i++) 
            if (pa[i]) pa[ pa[i] ]=i;
    //    for (int i=1; i<=N; i++) printf("%d   ",pa[i]); puts("");
        memset(f,-1,sizeof(f));
        for (int x=0; x<=2; x++)
            for (int y=0; y<=2; y++)
                ans=(ans+dp(1,N,x,y))%P;
        printf("%d
    ",ans);
        return 0;
    }
    149D

    Codeforces-219D

    题目大意:给出一棵单向边组成的“树”,求出用最小的操作使得令一个点为根时,这个点能到达其余所有点,每次操作是将一条边反向,并输出可行的点。

    题解:这个题刚上来觉得有些蛋疼,想的仅仅是单向连边然后从每个点拓展,记录一下答案,然后再整个跑一下统计答案,感觉求出最小操作数是可以的,但是没法所有最小操作数可行的点。

    但启发了思路,其实这题很简单,正常的连边,记录一下边的正反,树形dp两遍。

    第一遍是强行以1为根,$f[x]$表示以$x$为根的子树在以1为根的情况下需要反转边几次,这个随意搞搞就出来了,得到了$f[]$之后,再进行一遍树形dp就可以得到以各点为整棵树根的答案,然后就可以了。

    Code:

    #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 200010
    int N;
    struct EdgeNode{int next,to,opt;}edge[MAXN<<1];
    int cnt=1,head[MAXN];
    inline void AddEdge(int u,int v,int k) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v; edge[cnt].opt=k;}
    inline void InsertEdge(int u,int v) {AddEdge(u,v,1); AddEdge(v,u,0);}
    int f[MAXN],g[MAXN];
    inline void DFS_1(int now,int last)
    {
        for (int i=head[now]; i; i=edge[i].next)
            if (edge[i].to!=last)
                g[edge[i].to]=edge[i].opt,
                DFS_1(edge[i].to,now),
                f[now]+=f[edge[i].to]+(g[edge[i].to]? 0:1);
    }
    inline void DFS_2(int now,int last)
    {
        for (int i=head[now]; i; i=edge[i].next)
            if (edge[i].to!=last)
                f[edge[i].to]=f[now]+(g[edge[i].to]? 1:-1),DFS_2(edge[i].to,now);
    }
    int main()
    {
        N=read();
        for (int i=1,x,y; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y);
        DFS_1(1,0);
        DFS_2(1,0);
        int ans=0x7fffffff;
        for (int i=1; i<=N; i++) ans=min(ans,f[i]);
        printf("%d
    ",ans);
        for (int i=1; i<=N; i++) if (f[i]==ans) printf("%d ",i);
        return 0;    
    } 
    219D

    Codeforces-455B

    题目大意:给出N个字符串,两个人进行K轮游戏,每个人每次添加一个字符,要求组成的字串为给出字符串中任意串的前缀,交替进行,直到不能放符合条件的字符为败,上一轮游戏的败者为下轮的先手,整个过程的胜者,定义为第K局结束的胜者。

    题解:这题真的有点蛋疼..又把博弈和树结合起来。

    根据题目中游戏的定义,肯定先会联想到Trie树,就转换成两个人在Trie树上博弈,因为树的形态是一定的,所以先手和后手的每种状态的结局也是确定不变的,所以可以进行树形dp求得;

    首先Trie树的叶节点深度显然先手必胜/必败,然后可以统计出每个非叶节点是否存在必胜、必败子节点。

    这个博弈是最标准的博弈,先手必胜当子节点中存在必胜态,后手必胜当所有子节点全是必胜态,这样dp一遍就能得到所有节点的状态;

    然后讨论答案:

    1.如果初始的先手玩家存在必胜套路同时也存在失败套路,显然他可以一直输,最后一局翻盘;

    2.如果初始玩家先手没有必胜套路,也就是说后手玩家一定存在至少一种方法可以令先手必败,这样先手一定一直输;

    3.如果先手玩家只有必胜套路,怎么都不会输,那么整个过程的胜者就取决于K轮数的奇偶了;

    Code:

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<cstdio>
    using namespace std;
    #define MAXN 100010
    int N,K;
    char s[MAXN];
    int son[MAXN][27],sz=1;
    inline void Insert(char str[])
    {
        int now=1,len=strlen(str+1);
        for (int i=1; i<=len; i++)
            if (son[now][str[i]-'a'+1]) now=son[now][str[i]-'a'+1];
                else son[now][str[i]-'a'+1]=++sz,now=sz;
    }
    int tree[MAXN][2];
    inline void DFS(int now,int opt)
    {
        bool w=opt^1,f=opt^1,flag=0;
        for (int i=1; i<=26; i++)
            if (son[now][i])
                {
                    flag=1;
                    DFS(son[now][i],opt^1);
                    if (opt) w=w||tree[son[now][i]][0],f=f||tree[son[now][i]][1];
                        else w=w&&tree[son[now][i]][0],f=f&&tree[son[now][i]][1];
                }
        if (flag) tree[now][0]=w,tree[now][1]=f;
            else  tree[now][opt]=1,tree[now][opt^1]=0;
    }
    int main()
    {
        scanf("%d%d",&N,&K);
        for (int i=1; i<=N; i++) scanf("%s",s+1),Insert(s);
        DFS(1,1);
    //    for (int i=1; i<=sz; i++) printf("%d   %d
    ",tree[i][0],tree[i][1]);
        if (!tree[1][0]) {puts("Second"); return 0;}
        if (tree[1][1]) {puts("First"); return 0;}
        if (K&1) puts("First"); else puts("Second");
        return 0;
    }
    455B

    Codeforces-577B

    题目大意:给出N个数,取任意个数,问能否使取得数的和%M恰好为0

    题解:这个题还是很水的,可以直接一个01背包水过去,但是复杂度是$O(NM)$的,过不了,然后就得利用鸽巢原理。

    就是说,N个数,共有N种前缀和,而%M得到的值,只有M种,所以,根据鸽巢原理,当N>M时,一定有至少两个前缀和%M后得到的值是相同的,所以N>M一定能取到%M=0的情况,所以是必然YES。

    剩下的做个背包,复杂度$O(M^{2})$。

    Code:

    #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;
    }
    int N,M,a[1000010];
    bool dp[1010][1010];
    inline bool DP()
    {
        dp[0][0]=1;
        for (int i=1; i<=N; i++)
            for (int j=M-1; j>=0; j--)
                if (dp[i-1][((j-a[i])%M+M)%M]) {dp[i][j]=1; if (!j) return 1;}
                    else if (dp[i-1][j]) dp[i][j]=1;
        return 0;
    }
    int main()
    {
        N=read(),M=read();
        for (int i=1; i<=N; i++) a[i]=read();
        if (N>M) return puts("YES"),0;
            else return puts(DP()? "YES":"NO"),0;
    }
    577B

    Codeforces-274B

    题目大意:给出一棵树,每个点有点权,每次可以处理一条从根开始的一些相互连通的点,可以对这些点的点权+1,或-1,问最少需要几次才能使树中所有节点权值全部为0

    题解:这个题还是很容易的,每次改变必须从根开始,所以肯定贪心的从叶子一次一次的变成0,这样$dp[0/1][x]$第$x$节点的最多+1/-1多少次。

    一开始想的是,这个东西可以通过它的儿子的$dp[0/1]$值相加得到,但是立马发现了不对,因为一次改变可以是根到两个它的儿子一起变,这样他只会改变1次,所以直接取max就好了。

    然后初步得到$dp[0/1][x]$之后,就得到了当前点的权值是多少,然后再直接计算把这个节点变成0,需要+1/-1多少次,统计起来即可。

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    using namespace std;
    #define LL long long
    inline LL read()
    {
        LL 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;
    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 dp[2][MAXN],val[MAXN];
    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);
                    dp[0][now]=max(dp[0][now],dp[0][edge[i].to]);
                    dp[1][now]=max(dp[1][now],dp[1][edge[i].to]);
                }
        val[now]=val[now]+dp[0][now]-dp[1][now];
        dp[0][now]+= val[now]<0? -val[now]:0;
        dp[1][now]+= val[now]>0?  val[now]:0;
    }
    int main()
    {
        N=read();
        for (int i=1,x,y; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y);
        for (int i=1; i<=N; i++) val[i]=read();
        DFS(1,0);
    //    for (int i=1; i<=N; i++) printf("<%d,%d>
    ",dp[0][i],dp[1][i]);
        printf("%I64d
    ",dp[1][1]+dp[0][1]);
        return 0;
    }
    274B

    Codeforces-455C

    题目大意:给出N个点和M条边,要求支持两种操作:1.询问x所在树的直径 2.给出x,y合并x,y所在的树,且合并的后的直径尽量小。

    题解:求树的直径随便求啊,两遍dfs就可以得到,合并操作需要考虑。

    两树相连合并,要想合并后的直径最大,显然是用两个树直径的端点相连;最小,显然是将两个直径的中点相连,这样合并后的直径就可以算出来了$$L_{xy}=max(L_{x},L_{y},frac{L_{x}+1}{2}+frac{L_{y}+1}{2}+1)$$

    然后就可以用并查集维护一下。

    利用dfs时间戳就可以对初始不同的树划分,然后对每棵不同的树进行两遍dfs,在求直径的时候,注意单点的情况;再每棵树用一个并查集维护,这题就可以了。

    Code:

    #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 300010
    int N,M,Q;
    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);}
    int deep[MAXN],side,l,belong[MAXN],dfn;
    namespace UnionFind
    {
        int f[MAXN],L[MAXN];
        inline void init() {for (int i=1; i<=dfn; i++) f[i]=i;}
        inline int F(int x) {if (f[x]==x) return x; else return f[x]=F(f[x]);}
        inline void Merge(int x,int y)
        {
            if (F(x)==F(y)) return;
            int fx=F(x),fy=F(y);
            L[fx]=max( max(L[fx],L[fy]) , (L[fx]+1)/2+(L[fy]+1)/2+1 );
            f[fy]=fx;
        }
    }
    using namespace UnionFind;
    inline void DFS(int now,int last)
    {
        belong[now]=dfn;
        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);
                }
        if (deep[now]>l) l=deep[now],side=now;
    }
    inline void GetL(int now)
    {
        dfn++; side=now;
        l=0; deep[now]=0; DFS(now,0);
        l=0; deep[side]=0; DFS(side,0);
        L[dfn]=l;
    }
    int main()
    {
        N=read(); M=read(); Q=read();
        for (int i=1,x,y; i<=M; i++) x=read(),y=read(),InsertEdge(x,y);
        for (int i=1; i<=N; i++) if (!belong[i]) GetL(i);
        init();
    //    for (int i=1; i<=N; i++) printf("%d ",belong[i]); puts("");
        while (Q--)
            {
                int opt=read(),x,y;
                switch (opt)
                    {
                        case 1: x=read(); printf("%d
    ",L[ F(belong[x]) ]); break;
                        case 2: x=read(),y=read(); Merge(belong[x],belong[y]); break;
                    }
    //            for (int i=1; i<=N; i++) printf("%d   ",L[F(belong[i])]); puts("");
            }
        return 0;
    }
    455C

    Codeforces-486D

    题目大意:给出一棵树,求树中一个联通子图,使得点权最大的点和点权最小的点点权差值不超过d,问有多少种联通子图。

    题解:看了数据范围,发现$O(N^{2})$就可以,所以暴力就好,因为是联通子图,所以枚举以每个点为最大/最小的点,并向相邻的点暴力,统计答案即可。

    唯一稍微蛋疼一点的,就是如果有权值相同的可能会重复计算,这样限制一下点的序号的就可以了

    Code:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    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 P 1000000007
    #define MAXN 3010
    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);}
    int N,a[MAXN],d,ans,dp[MAXN],s;
    inline bool DFS(int now,int last)
    {
        if (a[now]+d<a[s]) return 0;
        if (a[now]==a[s] && s<now) return 0;
        if (a[now]>a[s]) return 0;
        dp[now]=1;
        for (int i=head[now]; i; i=edge[i].next)
            if (edge[i].to!=last)
                {
                    bool f=DFS(edge[i].to,now);
                    if (f) dp[now]=((LL)dp[now]*(dp[edge[i].to]+1))%P;
                }
        return 1;
    }
    int main()
    {
        d=read(); N=read();
        for (int i=1; i<=N; i++) a[i]=read();
        for (int i=1,x,y; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y);
        for (int i=1; i<=N; i++) s=i,DFS(i,0),ans=(ans+dp[i])%P;
        printf("%d
    ",ans);
        return 0;
    }
    486D

    Codeforces-245H

    题目大意:给出一个字符串,和Q个询问,每次询问$[l,r]$中,回文串的数量。

    题解:愚蠢的区间dp,可以先$O(N^{2})$的预处理出$ok[l][r]$表示$[l,r]$是否是回文串,然后就可以$O(N^{2})$一遍dp求出答案。$$dp[l][r]=dp[l+1][r]+dp[l][r-1]-dp[l+1][r-1]+ok[l][r]$$

    Code:

    #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 5010
    char s[MAXN];
    int Q,N,dp[MAXN][MAXN],ok[MAXN][MAXN],x,y;
    int main()
    {
        scanf("%s",s+1); N=strlen(s+1);
        for (int i=1; i<=N; i++) ok[i][i]=1;
        for (int len=2; len<=N; len++)
            for (int l=1,r=l+len-1; l+len-1<=N; l++,r=l+len-1)
                ok[l][r]=len==2? s[l]==s[r] : s[l]==s[r]&&ok[l+1][r-1];
        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=l+len-1)
                dp[l][r]=dp[l+1][r]+dp[l][r-1]-dp[l+1][r-1]+ok[l][r];
        Q=read();
        while (Q--) x=read(),y=read(),printf("%d
    ",dp[x][y]);
        return 0;
    }
    245H

    Codeforces-571B

    题目大意:给出一个序列,可以任意调整序列的顺序,使得给出的式子的值最小

    题解:这题有点意思,可以先贪心的,先对整个序列排序,然后对序列“分块”,最后进行一遍dp,求出答案。

    Code:

    #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 300010
    int N,K,a[MAXN],dp[5010][5010];
    int main()
    {
        N=read(),K=read();
        for (int i=1; i<=N; i++) a[i]=read();
        sort(a+1,a+N+1);
        int sz1=N%K,sz2=K-N%K,len1=N/K+1,len2=N/K;
        a[0]=a[1];
        for (int i=0; i<=sz1; i++)
            for (int j=0,k; j<=sz2; j++)
                k=(i-1)*len1+j*len2,
                dp[i][j]=max( dp[i][j] , (i? (dp[i-1][j]+a[k+1]-a[k]) : dp[i][j] ) ),
                k=i*len1+(j-1)*len2,
                dp[i][j]=max( dp[i][j] , (j? (dp[i][j-1]+a[k+1]-a[k]) : dp[i][j] ) );
    //    for (int i=0; i<=sz1; i++,puts(""))
    //        for (int j=0; j<=sz2; j++)
    //            printf("%d  ",dp[i][j]);
        printf("%d
    ",a[N]-a[1]-dp[sz1][sz2]);
        return 0;
    }
    571B

    Codeforces-494B

    题目大意:给出两个字符串,求有多少种第一个串的拆分出的子串,使之全部包含第二个串。

    题解:先用KMP求出匹配的位置,然后$f[i]$表示第$i$位的答案,$g[i]=sum f[j]$表示前$i$位的答案,也就是$f[i]$的前缀和,转移的时候用$g[i]$前缀和优化。

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    using namespace std;
    #define P 1000000007
    #define MAXN 100010
    char a[MAXN],b[MAXN];
    int f[MAXN],g[MAXN],sum[MAXN],ok[MAXN],Next[MAXN],N,M;
    inline void GetNext()
    {
        Next[1]=0;
        for (int i=2,j=0; i<=M; i++)
            {
                while (j && b[j+1]!=b[i]) j=Next[j];
                if (b[j+1]==b[i]) ++j;
                Next[i]=j;
            }
    }
    inline void KMP()
    {
        for (int i=1,j=0; i<=N; i++)
            {
                while (j && b[j+1]!=a[i]) j=Next[j];
                if (b[j+1]==a[i]) ++j;
                if (j==M) ok[i]=1;
            }
    }
    int main()
    {
        scanf("%s%s",a+1,b+1); N=strlen(a+1),M=strlen(b+1);
        GetNext();
        KMP();
    //    for (int i=1; i<=M; i++) printf("%d  ",Next[i]); puts("");
        for (int i=1; i<=N; i++)
            if (!ok[i])
                f[i]=f[i-1],g[i]=(g[i-1]+f[i])%P,sum[i]=(sum[i-1]+g[i])%P;
            else 
                f[i]=sum[i-M]+i-M+1,g[i]=(g[i-1]+f[i])%P,sum[i]=(sum[i-1]+g[i])%P;
        printf("%d
    ",g[N]);
        return 0;
    }
    494B

    Codeforces-335B

    题目大意:给出一个字符串长度为$5*10^{4}$,求这个如果这个字符串存在长度恰好为100的回文子串,则输出,否则输出最长回文子串。

    题解:很容易想到区间dp,但是复杂度是$O(N^{2})$的,显然不行,那么利用鸽巢原理,当字符串长度$>=2600$时,显然至少有一种字符数量是$>=100$的,所以可以直接输出100个这个字符。

    所以需要DP的实际是$N<=2600$这样是可以区间dp的,也很简单。要输出方案,开始的想法是利用string直接记录,但是会被卡MLE,所以需要在DP完之后进行一遍dfs统计方案。

    值得注意的一点,就算$N<=2600$,也不代表就可以直接dp完输出方案,因为答案也有可能$>100$,所以还是得特别考虑!!!!(这里一开始忽略了)

    Code:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<string.h>
    using namespace std;
    char s[50010];
    string ans[2601][2601];
    int N,dp[2601][2601],a[27];
    
    int main()
    {
        scanf("%s",s+1); N=strlen(s+1);
        if (N>=2600)
            {
                for (int i=1; i<=N; i++) a[s[i]-'a'+1]++;
                for (int i=1; i<=26; i++) 
                    if (a[i]>=100)
                        {for (int j=1; j<=100; j++) printf("%c",char(i+'a'-1)); return 0;}
            }
        for (int i=1; i<=N; i++) dp[i][i]=1,ans[i][i]=s[i];
        for (int len=2; len<=N; len++)
            for (int l=1,r=l+len-1; l+len-1<=N; l++,r=l+len-1)
                {
                    if (s[l]==s[r]) 
                        dp[l][r]=dp[l+1][r-1]+2,
                        ans[l][r]=s[l]+ans[l+1][r-1]+s[r];
                    else 
                        dp[l][r]=max(dp[l+1][r],dp[l][r-1]),
                        ans[l][r]=dp[l+1][r]>dp[l][r-1]? ans[l+1][r] : ans[l][r-1];
                    if (dp[l][r]==100)
                        {
                            for (int j=0; j<ans[l][r].length(); j++) putchar(ans[l][r][j]);
                            return 0;
                        }
                }
        for (int j=0; j<ans[1][N].length(); j++) putchar(ans[1][N][j]);
        return 0;
    }
    CF355B(MLE)
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<string.h>
    using namespace std;
    char s[50010],ans[50010];
    int N,dp[2601][2601],a[27],cnt;
    inline void print(int l,int r)
    {
        if (l>r) return;
        if (l==r) {ans[++cnt]=s[l]; return;}
        if (s[l]==s[r]) ans[++cnt]=s[l],print(l+1,r-1),ans[++cnt]=s[r];
            else
                if (dp[l+1][r]>dp[l][r-1]) print(l+1,r); else print(l,r-1);
    }
    int main()
    {
        scanf("%s",s+1); N=strlen(s+1);
        if (N>=2600)
            {
                for (int i=1; i<=N; i++) a[s[i]-'a'+1]++;
                for (int i=1; i<=26; i++) 
                    if (a[i]>=100)
                        {for (int j=1; j<=100; j++) printf("%c",char(i+'a'-1)); return 0;}
            }
        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=l+len-1)
                {
                    if (s[l]==s[r]) 
                        dp[l][r]=dp[l+1][r-1]+2; 
                    else 
                        dp[l][r]=max(dp[l+1][r],dp[l][r-1]);
                    if (dp[l][r]==100)
                        {
                            print(l,r);
                            for (int i=1; i<=cnt; i++) putchar(ans[i]);
                            return 0;
                        }
                }
        print(1,N);
        if (cnt>=100) 
            {
                for (int i=1; i<=50; i++) putchar(ans[i]);
                for (int i=50; i>=1; i--) putchar(ans[i]);
                return 0;
            }
        for (int i=1; i<=cnt; i++) putchar(ans[i]);
        return 0;
    }
    CF355B

    Codeforces-721C

    题目大意:给出一个N个点M条边的DAG,和一个限制条件T,问从1~N路径长度<=T时最多经过的点数,并输出方案。

    题解:很显然是拓扑dp,$dp[i][j]$表示到$i$这个点经过点数为$j$的最短路径长度,拓扑排序时$N^{2}$的暴力转移即可。

    记录一下路径,最后答案倒着扫一遍后倒推出方案。

    Code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<queue>
    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 N,M,T,ans[MAXN],tot,d[MAXN],dp[MAXN][MAXN],from[MAXN][MAXN];
    struct EdgeNode{int next,to,d;}edge[MAXN];
    int head[MAXN],cnt=1;
    inline void AddEdge(int u,int v,int w) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v; edge[cnt].d=w;}
    inline void toposort()
    {
        queue<int>q;
        for (int i=1; i<=N; i++) if (!d[i]) q.push(i);
        dp[1][1]=0;
        while (!q.empty())
            {
                int now=q.front(); q.pop();
                for (int i=head[now]; i; i=edge[i].next)
                    {
                        for (int j=2; j<=N; j++)
                            if (dp[now][j-1]+edge[i].d<dp[edge[i].to][j])
                                dp[edge[i].to][j]=dp[now][j-1]+edge[i].d,from[edge[i].to][j]=now;
                        if (!--d[edge[i].to]) q.push(edge[i].to);
                    }
            }
    }
    int main()
    {
        N=read(),M=read(),T=read();
        for (int i=1,x,y,z; i<=M; i++) x=read(),y=read(),z=read(),AddEdge(x,y,z),d[y]++;
        memset(dp,63,sizeof(dp));
        toposort();
        for (int i=N; i; i--) 
            if (dp[N][i]<=T) 
                {
                    printf("%d
    ",i);
                    for (int now=N; now; now=from[now][i],i--) ans[++tot]=now;
                    for (int j=tot; j; j--) printf("%d ",ans[j]);
                    break;
                }
        return 0;
    }
    CF721C

    Codeforces-212E

    题目大意:每个点可以涂两种颜色,满足没有相邻的两个点颜色相同,求最大染色方案时的方案数和两个颜色的数目。

    题解:显然最大染色数为N-1,可以枚举每个节点为根,其子树可以组成的答案,利用背包求解。

    #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 5050
    int N,M,ans,dp[MAXN][MAXN],size[MAXN],ok[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);}
    inline void DFS(int now,int last)
    {
        dp[now][0]=1; 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];
                    for (int j=M; j>=0; j--)
                        if (j+size[edge[i].to]<=M) 
                            dp[now][j+size[edge[i].to]]=dp[now][j]? 1:dp[now][j+size[edge[i].to]];
                }
        for (int j=M; j>=0; j--)
            if (j+N-size[now]<=M)
                dp[now][j+N-size[now]]=dp[now][j]? 1:dp[now][j+N-size[now]];
    }
    int main()
    {
        N=read(); M=N-1;
        for (int i=1,x,y; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y);
        DFS(1,0);
    //    for (int i=1; i<=N; i++) printf("%d  ",size[i]); puts("");
        for (int i=1; i<=N; i++)
            for (int j=1; j<=M-1; j++)
                if (dp[i][j] && !ok[j]) ok[j]=1,ans++;
        printf("%d
    ",ans);
        for (int i=1; i<=M-1; i++) if (ok[i]) printf("%d %d
    ",i,M-i);
        return 0;
    }
    CF212E

  • 相关阅读:
    Go
    Go
    Go
    Go
    Go
    Go
    爬虫常用相关库
    Go
    python基础第7天(day19)
    python基础第五天(day17)元组,集合,字符串操作 字符编码:
  • 原文地址:https://www.cnblogs.com/DaD3zZ-Beyonder/p/6006438.html
Copyright © 2020-2023  润新知