zoukankan      html  css  js  c++  java
  • 普及DP选做(终于开始做DP题了)

    啊这


    1

    断环为链。
    注意到两座仓库的距离不超过 (lfloor frac{n-1}2 floor), 所以可以枚举算出每个仓库的最大可能代价, 然后用单调队列优化下就可以 (O(N)) 了(单调队列里的每个点都记录其 (A) 和其在链中的位置)。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1e6+15;
    
    int n, a[N*2+5];
    int q[N*2+5], he, ta;
    int ans=0;
    
    int main()
    {
        cin>>n;
        for(int i=1;i<=n;++i) {
            scanf("%d",&a[i]);
            a[n+i]=a[i];
        }
        q[he=ta=1] = 1;
        for(int i=2;i<=(n<<1);++i) {
            while(he<=ta && i-q[he]>(n-1)/2) ++he;
            if(he<=ta) ans=max(ans, a[i]+a[q[he]]+i-q[he]);
            while(he<=ta && a[q[ta]]-q[ta]<=a[i]-i) --ta;
            q[++ta] = i;
        }
        cout << ans;
        return 0;
    }
    

    2

    首先建有向图, 边从先修课指向本课程, 发现建出了森林。
    对每棵树分别求解在其中选 ([0,m]) 个课程能拿到的最大得分, 最后用背包合并即可。
    对每棵树求解的方法也用背包做。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 305;
    
    int n,m,sc[N];
    vector<int> sons[N];
    int f[N][N];
    void sol(int x) {
        f[x][0] = 0;
        if(!sons[x].empty()) {
        for(int i=0;i<(int)sons[x].size();++i) {
            int y=sons[x][i];
            sol(y);
            for(int j=m;j>=1;--j)
                for(int k=1;k<=j;++k)
                    f[x][j] = max(f[x][j], f[x][j-k]+f[y][k]);
        }
        }
        if(x!=0) {
            for(int i=m;i>=1;--i)
                f[x][i] = f[x][i-1]+sc[x];
        }
    }
    
    int main()
    {
        scanf("%d%d", &n,&m);
        for(int i=1;i<=n;++i) {int fa;
            scanf("%d%d", &fa,&sc[i]);
            sons[fa].push_back(i);
        }
        sol(0);
        cout << f[0][m];
        return 0;
    }
    

    3

    (|D-P|) 塞进状态里, 求最大 (D+P), 发现 (最多状态数 = 4000*800), 可做。
    (f[i][j][k]) 表前 (i) 个, 选了 (j) 个, 差值为 (k)
    最多转移总数也是 (O(4000*800)) 的。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 205;
    const int M = 25;
    const int base = 400;
    
    int n,m,p[N],d[N],sp[N],sd[N];
    int f[N][M][805]; // 0为正, 1为负
    int pre[N][M][805];
    inline void cmax(int &x,int y) {x=max(x,y); }
    inline bool ok(int k) {return k>=-400&&k<=400;}
    
    void out(int i,int j,int k,int dd,int pp) {
        if(i==0&&j==0&&k==0) {
            printf("Best jury has value %d for prosecution and value %d for defence:
    ",pp,dd);
            return;
        }
        if(pre[i][j][k+base]) {
            out(i-1,j-1,k-(d[i]-p[i]),dd+d[i],pp+p[i]);
            printf(" %d", i);
        } else {
            out(i-1,j,k,dd,pp);
        }
    }
    void sol() {
        memset(pre,0,sizeof pre);
        memset(f,-0x3f,sizeof f);
        f[0][0][0+base]=0;
        
        int ansi,ansj,ansk=10000,ansv=-1;
        for(int i=1;i<=n;++i) {
            for(int j=0;j<=min(i,m);++j) {
                for(int k=-400;k<=400;++k) {
                    f[i][j][k+base] = f[i-1][j][k+base];
                    pre[i][j][k+base] = 0;
                    int prek = k-(d[i]-p[i]);
                    if(j>0 && ok(prek) && f[i-1][j-1][prek+base]+(d[i]+p[i])>f[i][j][k+base]) {
                        f[i][j][k+base] = f[i-1][j-1][prek+base]+(d[i]+p[i]);
                        pre[i][j][k+base] = 1;
                        // cmax(f[i][j][k+base], f[i-1][j-1][prek+base]+(d[i]+p[i]));
                    }
                    if(j==m&&f[i][j][k+base]>=0) //cout<<k<<' '<<f[i][j][k+base]<<'
    ';
                    {
                        if(abs(k)<abs(ansk)) {
                            ansv=f[i][j][k+base];
                            ansi=i,ansj=j,ansk=k;
                        }
                        else if(abs(k)==abs(ansk) && f[i][j][k+base]>ansv) {
                            ansv=f[i][j][k+base];
                            ansi=i,ansj=j,ansk=k;
                        }
                    }
                }
            }
        }
        
        out(ansi,ansj,ansk,0,0);
        cout<<"
    
    ";
    }
    
    int main()
    {
        int id = 0;
        while(scanf("%d%d", &n,&m)==2 && n && m) {
            for(int i=1;i<=n;++i) { scanf("%d%d",&p[i],&d[i]);
                sp[i]=sp[i-1]+p[i], sd[i]=sd[i-1]+d[i];
            }
            printf("Jury #%d
    ", ++id);
            sol();
        }
        return 0;
    }
    

    4

    先随便求一个点的最大流,然后换根。
    有点细节。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 2e5+15;
    
    int n, deg[N];
    int ct,hd[N],nt[N<<1],vr[N<<1],vl[N<<1];
    void ad(int u,int v,int w) {
        vr[++ct]=v, vl[ct]=w, nt[ct]=hd[u], hd[u]=ct;
    }
    
    int f[N];
    void dfs(int x,int fa) {
        f[x]=0;
        for(int i=hd[x];i;i=nt[i]) {int y=vr[i]; if(y==fa)continue;
            dfs(y,x);
            if(deg[y]==1&&y!=1) f[x] += vl[i];
            else f[x] += min(f[y], vl[i]);
        }
    }
    void getans(int x,int fa) {
        for(int i=hd[x];i;i=nt[i]) {int y=vr[i]; if(y==fa)continue;
            if(deg[x]==1) f[y] = f[y] + vl[i];
            else f[y] = f[y] + min(vl[i], f[x]-min(vl[i],f[y]));
            getans(y,x);
        }
    }
    
    int main()
    {
        int T;cin>>T;while(T--) {
            scanf("%d",&n);
            ct=0;
            memset(deg,0,sizeof deg);
            memset(hd,0,sizeof hd);
            for(int i=1;i<n;++i) { int u,v,w;
                scanf("%d%d%d", &u,&v,&w); ad(u,v,w); ad(v,u,w);
                ++deg[u], ++deg[v];
            }
            dfs(1,0);
            getans(1,0);
            for(int i=2;i<=n;++i) f[i]=max(f[i],f[i-1]);
            cout<<f[n]<<'
    ';
        }
        return 0;
    }
    

    5

    码农题。
    暴力
    (f[i][k][l][r][0/1][0/1]) 表示前 (i) 行选了 (k) 个, 第 (i) 行选了 ([l,r]), 左右边界状态分别是 ({递增(1),递减(0)}) 之一时的最大权。
    总状态数 (1518750*2), 加上转移要枚举 (O(341718750*2)) 次, 很难受。

    ( ext{Rep(i,1,n);}) 保平安。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 16;
    const int inf = 2100000000;
    #define Rep(i,l,r) for(int i=(l);i<=(r);++i)
    
    int n,m,k,sc[N][N],ssc[N][N];
    
    int f[N][226][N][N][2][2];
    int _pi[N][226][N][N][2][2], _pj[N][226][N][N][2][2], _pl[N][226][N][N][2][2];
    int _pr[N][226][N][N][2][2], _px[N][226][N][N][2][2], _py[N][226][N][N][2][2];
    inline void ud(int i,int j,int l,int r,int x,int y, int ii,int jj,int ll,int rr,int xx,int yy) {
        _pi[i][j][l][r][x][y] = ii;
        _pj[i][j][l][r][x][y] = jj;
        _pl[i][j][l][r][x][y] = ll;
        _pr[i][j][l][r][x][y] = rr;
        _px[i][j][l][r][x][y] = xx;
        _py[i][j][l][r][x][y] = yy;
    }
    
    int cost(int i,int l,int r) {return ssc[i][r]-ssc[i][l-1]; }
    
    int ans,ansi,ansj,ansl,ansr,ansx,ansy;
    bool vis[N][N];
    void tag(int i,int j,int l,int r,int x,int y) {
        if(j==0) return;
        Rep(I,l,r) vis[i][I] = 1;
        tag(_pi[i][j][l][r][x][y], _pj[i][j][l][r][x][y], _pl[i][j][l][r][x][y], 
                _pr[i][j][l][r][x][y], _px[i][j][l][r][x][y], _py[i][j][l][r][x][y]);
    }
    
    int main() {
        scanf("%d%d%d",&n,&m,&k);
        Rep(i,1,n)Rep(j,1,m) {scanf("%d", &sc[i][j]); ssc[i][j]=sc[i][j]+ssc[i][j-1]; }
        
        Rep(i,0,n)Rep(j,1,k)Rep(l,1,m)Rep(r,l,m)Rep(x1,0,1)Rep(y1,0,1) f[i][j][l][r][x1][y1] = -inf;
        Rep(i,0,n)Rep(l,1,m)Rep(r,l,m)Rep(x1,0,1)Rep(y1,0,1) f[i][0][l][r][x1][y1] = 0;
        
        Rep(i,1,n)Rep(j,1,k)Rep(l,1,m)Rep(r,l,m) {
            if(j<r-l+1) continue;
            //x=0递增 y=0递增
            //由x=0/1, y=0 转移来
            Rep(pl,1,l)Rep(pr,l,r) {
                int &now = f[i][j][l][r][0][0];
                Rep(x1,0,1) if(now<f[i-1][j-(r-l+1)][pl][pr][x1][0]+cost(i,l,r)) {
                    now = f[i-1][j-(r-l+1)][pl][pr][x1][0]+cost(i,l,r);
                    ud(i,j,l,r,0,0,  i-1,j-(r-l+1),pl,pr,x1,0);
                }
            }
            //x=0递增 y=1递减
            //由x=0/1, y=0/1 转移来
            Rep(pl,1,l)Rep(pr,r,m) {
                int &now = f[i][j][l][r][0][1];
                Rep(x1,0,1)Rep(y1,0,1) if(now<f[i-1][j-(r-l+1)][pl][pr][x1][y1]+cost(i,l,r)) {
                    now = f[i-1][j-(r-l+1)][pl][pr][x1][y1]+cost(i,l,r);
                    ud(i,j,l,r,0,1,  i-1,j-(r-l+1),pl,pr,x1,y1);
                }
            }
            //x=1递减 y=0递增
            //由x=1 y=0 转移来
            Rep(pl,l,r)Rep(pr,pl,r) {
                int &now = f[i][j][l][r][1][0];
                if(now<f[i-1][j-(r-l+1)][pl][pr][1][0]+cost(i,l,r)) {
                    now = f[i-1][j-(r-l+1)][pl][pr][1][0]+cost(i,l,r);
                    ud(i,j,l,r,1,0,  i-1,j-(r-l+1),pl,pr,1,0);
                }
            }
            //x=1递减 y=1递减
            //由x=1 y=0/1转移来
            Rep(pl,l,r)Rep(pr,r,m) {
                int &now = f[i][j][l][r][1][1];
                Rep(y1,0,1) if(now<f[i-1][j-(r-l+1)][pl][pr][1][y1]+cost(i,l,r)) {
                    now = f[i-1][j-(r-l+1)][pl][pr][1][y1]+cost(i,l,r);
                    ud(i,j,l,r,1,1,  i-1,j-(r-l+1),pl,pr,1,y1);
                }
            }
            if(j==k) {
                Rep(x1,0,1)Rep(y1,0,1) if(ans<f[i][j][l][r][x1][y1]) {
                  ans = f[i][j][l][r][x1][y1];
                  ansi=i, ansj=j, ansl=l, ansr=r, ansx=x1, ansy=y1;
                }
            }
            
        }
        printf("Oil : %d
    ", ans);
        tag(ansi,ansj,ansl,ansr,ansx,ansy);
        Rep(i,1,n)Rep(j,1,m) if(vis[i][j]) printf("%d %d
    ",i,j);
        return 0;
    }
    

    6

    这个问题说得比较傻逼一点就是分层网格图上的随机游走问题
    列转移方程后按行标号从大到小 递推+高消 即可。(我线代咋这么差啊)

    #include<bits/stdc++.h>
    using namespace std;
    #define Rep(i,l,r) for(int i=(l);i<=(r);++i)
    #define Dop(i,r,l) for(int i=(r);i>=(l);--i)
    #define lb double
    const int N = 1005;
    
    int n,m,x,y;
    lb a[N][N];
    void Gauss() {
        Rep(i,1,m) {
            lb tmp=a[i][i];
            a[i][i]/=tmp, a[i][i+1]/=tmp;
            if(i==m) continue;
            a[i][m+1]/=tmp;
            
            tmp = a[i+1][i];
            a[i+1][i] -= tmp*a[i][i], a[i+1][i+1] -= tmp*a[i][i+1], a[i+1][m+1] -= tmp*a[i][m+1];
        }
        Dop(i,m,2) {
            lb tmp=a[i-1][i];
            a[i-1][i] -= tmp*a[i][i], a[i-1][m+1] -= tmp*a[i][m+1];
        }
    }
    
    int main()
    {
        
        scanf("%d%d%d%d", &n,&m,&x,&y);
        n -= (x-1); x=1;
        
        if(m==1) {
            printf("%.4lf", (double)2 * (n - 1));
            return 0;
        }
        
        Rep(mdzz,1,n-1)
        {
            
            a[1][m+1] = a[1][m+1]/3+1;
            a[1][1]=(lb)2.0/3, a[1][2]=(lb)-1.0/3;
            Rep(i,2,m-1) {
                a[i][m+1] = a[i][m+1]/4+1;
                a[i][i]=(lb)3.0/4, a[i][i-1]=(lb)-1.0/4, a[i][i+1]=(lb)-1.0/4;
            }
            a[m][m+1] = a[m][m+1]/3+1;
            a[m][m]=(lb)2.0/3, a[m][m-1]=(lb)-1.0/3;
            
            Gauss();
        }
        
        printf("%.4lf", a[y][m+1]);
        return 0;
    }
    

    7

    换根大法好。
    发现一个点的答案可以分成 ({包含子树内节点的路, else}) , 包含子树内的很简单, 第二种可以换根, 转移的时候用下前后缀 (max{}) 就好了, 同时推荐此时用 ( ext{vector}) 存边。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 10005;
    
    int n, f[N], g[N];
    vector<int> vr[N],w[N],pre[N],suf[N],no[N];
    
    void dfs1(int x) {
        for(int i=0;i<(int)vr[x].size();++i) { int y=vr[x][i];
            dfs1(y);
            f[x] = max(f[x], f[y]+w[x][i]);
        }
        for(int i=0;i<(int)vr[x].size();++i) { int y=vr[x][i];
            pre[x][i] = f[y]+w[x][i];
            if(i!=0) pre[x][i] = max(pre[x][i], pre[x][i-1]);
        }
        for(int i=(int)vr[x].size()-1;i>=0;--i) { int y=vr[x][i];
            suf[x][i] = f[y]+w[x][i];
            if(i!=(int)vr[x].size()-1) suf[x][i] = max(suf[x][i],suf[x][i+1]);
        }
        for(int i=0;i<(int)vr[x].size();++i) {
            if(i!=0) no[x][i] = max(no[x][i], pre[x][i-1]);
            if(i!=(int)vr[x].size()-1) no[x][i] = max(no[x][i], suf[x][i+1]);
            // cout<<x<<' '<<vr[x][i]<<' '<<no[x][i]<<'
    ';
        }
    }
    
    void dfs2(int x) {
        for(int i=0;i<(int)vr[x].size();++i) { int y=vr[x][i];
            g[y] = max(no[x][i], g[x]) + w[x][i];
            dfs2(y);
        }
    }
    
    int main() {
        while(scanf("%d",&n)==1) {
            for(int i=1;i<=n;++i) {
                f[i]=g[i]=0;
                vr[i].clear(), w[i].clear(), pre[i].clear(), suf[i].clear();
                no[i].clear();
            }
            for(int i=2;i<=n;++i) {int fa,sc;
                scanf("%d%d",&fa,&sc);
                vr[fa].push_back(i), w[fa].push_back(sc), pre[fa].push_back(0), suf[fa].push_back(0);
                no[fa].push_back(0);
            }
            dfs1(1);
            dfs2(1);
            for(int i=1;i<=n;++i) printf("%d
    ", max(f[i],g[i]));
        }
    }
    

    8

    子问题1:
    暴力跳
    子问题2:
    暴力跳

    优化: 跳的过程可以优化,预处理下就好啦。

    对每个位置预处理出在这个位置轮到小 ({A,B}) 开车, 再开 ({2^?}) 次车, 到达的地方和小 ({A,B}) 分别开的路程就好了。

    两个问题都是用的一种优化方法, 这个方法可以封装成一个函数: (cost(i,x)) , 表示从 (i) 出发, 走不超过 (x) 的路程, 小 ({A,B}) 分别行驶的路程。

    每次 (cost(i,x))(O(log n)) 的, 算答案的总复杂度就是 (O(nlog n))

    预处理可以用 (set), 加上倍增也是 (O(n log n)) 的。

    (先写个暴力, 以后再写qwq)

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1e5+15;
    #define li long long
    #define int long long
    #define Rep(i,l,r) for(int i=(l);i<=(r);++i)
    
    int n,h[N];
    int d[1005][1005];
    
    li Acost, Bcost;
    void cost(int s,int x) {
    	Acost = Bcost = 0ll;
    	int nowp = s, nowman = 0;
    	while(Acost+Bcost < x) {
    		int fisd=2000000000, fis=0;
    		Rep(i,nowp+1,n) {
    			if(d[nowp][i]<fisd || (d[nowp][i]==fisd&&h[i]<h[fis])) {
    				fis=i, fisd=d[nowp][i];
    			}
    		}
    		int secd=2000000000, sec=0;
    		Rep(i,nowp+1,n) {
    			if(i==fis) continue;
    			if(d[nowp][i]<secd || (d[nowp][i]==secd&&h[i]<h[sec])) {
    				sec=i, secd=d[nowp][i];
    			}
    		}
    		
    		if(nowman==0) {
    			if(sec==0) break;
    			if(Acost+Bcost+secd > x) break;
    			Acost += secd;
    			nowp = sec;
    		} else {
    			if(fis==0) break;
    			if(Acost+Bcost+fisd > x) break;
    			Bcost += fisd;
    			nowp = fis;
    		}
    		nowman = 1-nowman;
    	}
    //	int nowp = s;
    //	for(int i=20;~i;--i) {
    //		if(Acost+Bcost + (Da[nowp][i]+Db[nowp][i])  <= x) {
    //			Acost += Da[nowp][i], Bcost += Db[nowp][i];
    //			nowp = Arive[nowp][i];
    //		}
    //	}
    }
    void sol1(int x) {
    	cost(1,x);
    	int ans=1;
    	li ansCosA = Acost, ansCosB = Bcost;
    	Rep(i,2,n) {
    	   // printf("%d %d
    ", ansCosA, ansCosB);
    		cost(i, x);
    		if(Acost+Bcost==0) continue;
    		if(ansCosA*Bcost > Acost*ansCosB) { // ansCosA/ansCosB > Acost/Bcost
    			ans = i;
    			ansCosA = Acost;
    			ansCosB = Bcost;
    		} else if(ansCosA*Bcost==Acost*ansCosB && h[i]>h[ans]) {
    			ans = i;
    			ansCosA = Acost;
    			ansCosB = Bcost;
    		}
    	}
    // 	printf("%d %d
    ", ansCosA, ansCosB);
    	printf("%d
    ", ans);
    	return;
    }
    
    void sol2(int s,int x) {
    	cost(s,x);
    	printf("%lld %lld
    ", Acost, Bcost);
    	return;
    }
    
    signed main()
    {
    	scanf("%lld",&n);
    	h[0] = 2000000000;
    	for(int i=1;i<=n;++i) scanf("%lld",&h[i]);
    	Rep(i,1,n)Rep(j,i+1,n) d[i][j] = abs(h[i]-h[j]);
    	
    	int m,x,s;
    	
    	//子问题1 
    	scanf("%lld",&x);
    	sol1(x);
    	
    	//子问题2 
    	scanf("%lld",&m);
    	while(m--)
    	{
    		scanf("%lld%lld",&s,&x);
    		sol2(s,x);
    	}
    	
    	return 0;
    }
    

    9

    哇, 刚才转移写窄了。
    我觉得这事件可以溯源, 以此找到我思维上的漏洞qwq。。
    状态设计如此:(f[i][j]) 表示第 (i) 个人等了 (j) 分钟的最优值。
    (j) 的上界: 当 (i) 能坐车走的时候, 最多等了 (m) 分钟, 如果这时候决定为了等下一个人而不发车, 那么最多等 (m) 分钟, 加起来就是 (2m) 分钟。
    转移情况:分三类, 发车时间大于等于下一个人的到达时间, 发车时间小于下一个人的到达时间且车回来的时间大于下一个人的到达时间, 发车时间小于下一个人的到达时间且车回来的时间小于等于下一个人的到达时间。

    我转移是怎么写窄的:我在写转移的时候是全程带着贪心的思想来写的, 压根没考虑到 “要考虑所有转移的可能性” 这件事, 这是一个长久以来的习惯(确信),这个习惯的养成可能因为我的 DP 启蒙题都是一类不适合作为启蒙题的神必玩意, 当然, 做那些题的不是我(now), 是我(before now), 所以也可能是题没问题, 我(before now)有问题。

    哇, 刚才转移写错了。
    这个思维上的漏洞挺严重的, 这说明在写转移的时候我没有看状态!

    我问题是真的大。

    代码:

    // loj 把代码格式化以后代码看起来挺丑的 o_o, 虽然格式化之前也很丑
    #include <bits/stdc++.h>
    using namespace std;
    const int N = 505;
    const int M = 105;
    
    int n, m, t[N];
    int f[N][2 * M + 5];
    
    void cmin(int &x, int y) { x = min(x, y); }
    
    int main() {
        freopen("bus.in", "r", stdin);
        freopen("bus.out", "w", stdout);
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; ++i) scanf("%d", &t[i]);
        sort(t + 1, t + 1 + n);
        memset(f, 0x63, sizeof f);
        for (int i = 0; i <= 2 * m; ++i) f[1][i] = i;
        for (int i = 1; i < n; ++i)
            for (int j = 0; j <= 2 * m; ++j) {
                if (t[i] + j >= t[i + 1])
                    cmin(f[i + 1][t[i] + j - t[i + 1]], f[i][j] + t[i] + j - t[i + 1]);
                else {
                    if (t[i] + j + m <= t[i + 1])
                        for (int k = 0; k <= 2 * m; ++k) cmin(f[i + 1][k], f[i][j] + k);
                    if (t[i] + j + m > t[i + 1])
                        for (int k = t[i] + j + m - t[i + 1]; k <= 2 * m; ++k) cmin(f[i + 1][k], f[i][j] + k);
                }
            }
        int ans = 2000000000;
        for (int i = 0; i <= 2 * m; ++i) ans = min(ans, f[n][i]);
        cout << ans;
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    

    10

    这题我压根就没想到减法原理。看了眼题解才继续做的。
    但是硬上还是麻烦, 于是就把全集当成满足限制 2 的。
    那么全集 (U) 的 size (|U|) 就是 (prod_{i=1}^n (Ms[i]+1)), 其中, (Ms[i] = prod_{1 le j le m, [a_{ij}!=0]} a_{ij})
    若是一种方案不满足条件, 必有一种超过 (lfloor frac{k}2 floor) 的食材(看起来肯定不会有两种啊)。

    我在想这个题的时候, 是想着用状态包罗万象, 而答案是固定那个超过 (lfloor frac{k}2 floor) 的行, 这似乎也算是一种后遗症。

    之后很多题解都有详细的介绍(虽然不见得清晰), 不细说了, 大概就是按这个思路硬解, 之后把两维状态合并。

    对这道题来说,我在初始化这件事上犯了一个错。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 105;
    const int M = 2005;
    const int base = 110;
    const int mod = 998244353;
    
    int n,m,a[N][M];
    long long pre[N][M],suf[N][M],f[N][3*N];
    
    int main() {
        freopen("meal.in", "r", stdin); freopen("meal.out","w",stdout);
        scanf("%d%d", &n,&m);
        for(int i=1;i<=n;++i) {
            for(int j=1;j<=m;++j) {
                scanf("%d", &a[i][j]);
            }
            pre[i][0]=suf[i][m+1]=0ll;
            for(int j=1;j<=m;++j)pre[i][j]=(1ll*pre[i][j-1]+a[i][j])%mod;
            for(int j=m;j>=1;--j)suf[i][j]=(1ll*suf[i][j+1]+a[i][j])%mod;
        }
        
        long long U=1ll, B = 0ll;
        for(int i=1;i<=n;++i) {
            long long u = 0ll;
            for(int j=1;j<=m;++j) u=(u+a[i][j])%mod;
            u = (u+1)%mod;
            U = (1ll*U*u) % mod;
        }
        
        for(int col=1;col<=m;++col) {
            memset(f,0,sizeof f);
            f[1][1+base] = a[1][col];
            f[1][-1+base] = (pre[1][col-1] + suf[1][col+1]) % mod;
            f[1][0+base] = 1ll;
            for(int i=2;i<=n;++i) {
                for(int j=-i;j<=i;++j) {
                    f[i][j+base] = (f[i-1][j+base] + f[i-1][j-1+base]*a[i][col]%mod)%mod;
                    f[i][j+base] += f[i-1][j+1+base] * ((pre[i][col-1]+suf[i][col+1])%mod) % mod;
                    f[i][j+base] %= mod;
                }
            }
            for(int j=1;j<=n;++j) B = (B+f[n][j+base])%mod;
        }
        cout << (mod+U-B)%mod - 1;
        fclose(stdin); fclose(stdout);
        return 0;
    }
    

    11

    暂时不想做结论题,先搁着。

    12

    从一开始我就把这道题当做一道 (DP) 想, 想要分析出一些不得了的性质, 这就是一个思维上的弱点。

    仔细分析下套个暴力数据结构就可以过了唉, 不过还需要一些二分的技巧, 以后再写吧。

  • 相关阅读:
    ModuleNotFoundError: No module named 'rest_framework_swagger'
    ModuleNotFoundError: No module named 'suit'
    HTTPS连接的前几毫秒发生了什么
    网络通信之 字节序转换原理与网络字节序、大端和小端模式
    聊聊HTTPS和SSL/TLS协议
    ECShop 调用自定义广告
    ECSHOP商城网站建设之自定义调用广告方法(二)
    https原理:证书传递、验证和数据加密、解密过程解析
    图解openssl实现私有CA
    SSL/TLS协议运行机制的概述
  • 原文地址:https://www.cnblogs.com/tztqwq/p/13198717.html
Copyright © 2011-2022 走看看