• [Contest on 2021.10.17] HustOJ 就是个 **


    ( m S)

    题目描述

    (n) 个球,每个球有 RGY 三种颜色,(sf Oxide) 觉得如果有两个相邻的球颜色相同很丑。
    她每次可以交换两个球,问至少交换多少次才能不丑。

    (1le nle 400)

    解法

    拿到这道题真的毫无思路… 有一个很重要的结论是:无论怎样交换,相同颜色的球的相对位置不会改变。

    这样问题就可以转化为:在长度为 (n) 的序列中填上三种颜色,每种颜色数量固定,填颜色的价值定义为 "逆序对的个数",且相邻位置颜色不同。具体而言,将原序列编号,第 (i)(j) 种球就对应它位置上的编号 ( m id)。填完颜色后,第 (i)(j) 种球有了新的位置 (k),就令 (p_k= m id),答案就是 (p) 中逆序对个数。

    (dp_{i,j,k,0/1/2}) 表示三种颜色分别填到 (i,j,k) 个,位置 (i+j+k) 的颜色是 (0/1/2)。颜色是用来判断相邻颜色是否相同的。复杂度 (mathcal O(n^3)),第一维需要滚动一下。

    代码

    #pragma GCC optimize(2)
    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
     
    template <class T>
    inline T read(const T sample) {
        T x=0; char s; bool f=0;
        while((s=getchar())>'9' or s<'0')
            f |= (s=='-');
        while(s>='0' and s<='9')
            x = (x<<1)+(x<<3)+(s^48),
            s = getchar();
        return f?-x:x;
    }
     
    template <class T>
    inline void write(const T x) {
        if(x<0) {
            putchar('-');
            write(-x);
            return;
        }
        if(x>9) write(x/10);
        putchar(x%10^48);
    }
    
    #include <cstring>
    #include <iostream>
    using namespace std;
    
    const int maxn = 405;
    const int inf = 0x3f3f3f3f;
    
    int dp[2][maxn][maxn][3],n;
    int pre[maxn][3],pos[maxn][3];
    char s[maxn];
    
    int add(int p,int j,int k,int b,int c) {
    	int ret=0;
    	if(pre[p][b]-j>0) ret += pre[p][b]-j;
    	if(pre[p][c]-k>0) ret += pre[p][c]-k;
    	return ret; 
    }
    
    int Sardine() {
    	for(int i=1;i<=n;++i) {
    		for(int j=0;j<3;++j)
    			pre[i][j]=pre[i-1][j];
    		if(s[i]=='R') {
    			++pre[i][0];
    			pos[pre[i][0]][0] = i;
    		}
    		else if(s[i]=='G') {
    			++pre[i][1];
    			pos[pre[i][1]][1] = i;
    		}
    		else {
    			++pre[i][2];
    			pos[pre[i][2]][2] = i;
    		}
    	}
    	memset(dp,0x3f,sizeof dp);
    	for(int i=0;i<3;++i)
    		dp[0][0][0][i]=0;
    	for(int i=0;i<=pre[n][0];++i) {
    		bool d=(i&1); int mn;
    		for(int j=0;j<=pre[n][1];++j)
    			for(int k=0;k<=pre[n][2];++k) {
    				if(!i and !j and !k) continue;
    				
    				for(int o=0;o<3;++o) dp[d][j][k][o]=inf;
    				
    				mn = inf;
    				for(int o=1;o<3;++o) mn = min(mn,dp[d^1][j][k][o]);
    				if(mn^inf) dp[d][j][k][0] = mn+add(pos[i][0],j,k,1,2);
    				
    				if(j) {
    					mn = inf;
    					for(int o=0;o<3;++o) 
    						if(o^1)
    							mn = min(mn,dp[d][j-1][k][o]);
    					if(mn^inf) dp[d][j][k][1] = mn+add(pos[j][1],i,k,0,2);
    				}
    				
    				if(k) {
    					mn = inf;
    					for(int o=0;o<2;++o) mn = min(mn,dp[d][j][k-1][o]);
    					if(mn^inf) dp[d][j][k][2] = mn+add(pos[k][2],i,j,0,1);
    				}
    			}
    	}
    	int ans = inf;
    	for(int i=0;i<3;++i)
    		ans = min(ans,dp[pre[n][0]&1][pre[n][1]][pre[n][2]][i]);
    	return ans==inf?-1:ans;
    }
    
    int main() {
    	n=read(9); scanf("%s",s+1);
    	print(Sardine(),'
    ');
    	return 0;
    }
    

    ( m Y)

    题目描述

    (n) 个人站成一个圈,每个人有 (a_i) 个球,下面要进行一次操作:

    • 每个人把一些它的球交给它左边的人,所有人 同时 进行。

    假设操作后每个人有 (b_i) 个球,记这个序列为 (B)。对于每 (B),它的价值为 (∏b_i)
    对于所有可能的 (B),你要计算它们的价值和。

    (1le nle 10^6,0le a_ile 10^9)

    解法

    神仙 (mathtt{dp}) 题,属于不看题解一辈子也想不出来的范畴。考试的时候大家都喊着做过做过,我也感觉自己好像做过… 结束后一看 ( m AtCoder),我那一场从这题开始就妹补了…

    小提示:这个 (mathtt{dp}) 也理解了蛮久的,表述时难免有些冗长。


    首先可以思考一下 (B) 最终的种数,这对正解也有启发:发现对于传球序列 (C),如果它差分之后相等,最终形成的 (B) 就是相同的 —— 所以我们不妨只计算 (min C_i=0) 的传球序列。种数是 (prod_{i=1}^n(a_i+1)-prod_{i=1}^na_i),一个容斥。

    现在要计算 (prod b_i),它的组合意义是 "对于每一 (B),每个人在自己最终的球中选择一个球的方案数,且 球不同"。但实际上,当最终的球数((b_i))相同时,即使球的来源不同,编号不同,由计算式我们仍认为这两种方案相同!也就是说,球不同只建立在最终的 (b_i) 个球内部。这也是不能直接 (mathtt{dp}) 的原因,这里的 "球不同" 和我们往常的理解有偏差,称往常的理解为 "严格的"。

    不妨就先按 球严格不同(mathtt{dp}),这其实就是 "曲线救国" 的思想,因为对于每一种 (B) 我们并不好计算贡献,但是对于 每种传球序列 就好算了,这是可以融合到转移方程里的。如何去重?类似上面的容斥,我们钦定传球序列不能有零,同样地 (mathtt{dp}) 一次即可。

    (dp_{i,0/1}) 分别为 "第 (i) 个人在原先球中选择,前 (i-1) 个人选球" 的选球方案数;"第 (i) 个人在第 (i-1) 人给的球中选择,前 (i) 个人选球" 的选球方案数。至于状态为什么如此鬼畜,可以看看后面的转移:

    • (dp_{i,0}leftarrow dp_{i-1,0})。此时需要计算 (i-1) 的选球方案。考虑 (i-1) 的传球方案:如果 (i-1)(a_i-a) 个球,就剩下 (a) 个球,他的选择方案为 (a) 种,所以系数就是 (sum_{j=1}^{a_i}j)
    • (dp_{i,0}leftarrow dp_{i-1,1})。考虑 (i-1) 的传球方案,系数为 (a_i+1)
    • (dp_{i,1}leftarrow dp_{i-1,1})。考虑 (i-1) 的传球方案:如果 (i-1)(a) 个球,(i) 的选择方案为 (a) 种,所以系数就是 (sum_{j=1}^{a_i}j)
    • (dp_{i,1}leftarrow dp_{i-1,0})。考虑 (i-1) 的传球方案:如果 (i-1)(a_i-a) 个球,就剩下 (a) 个球,二人在此种传球方案中的球选择方案为 (a(a_i-a)) 种,所以系数就是 (sum_{j=1}^{a_i}j(a_i-j)=a_isum_{j=1}^{a_i}j-sum_{j=1}^{a_i}j^2)

    完了?由于这是一个环,所以需要枚举第一个人的 (0/1) 状态。如枚举 (0) 状态,就 (dp_{1,0}leftarrow 1,dp_{1,1}leftarrow 0),最后答案是 (dp_{1,0}-1)(减去最开始加的 (1))。

    总共是 (mathcal O(n)) 的。

    代码

    #pragma GCC optimize(2)
    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
     
    template <class T>
    inline T read(const T sample) {
        T x=0; char s; bool f=0;
        while((s=getchar())>'9' or s<'0')
            f |= (s=='-');
        while(s>='0' and s<='9')
            x = (x<<1)+(x<<3)+(s^48),
            s = getchar();
        return f?-x:x;
    }
     
    template <class T>
    inline void write(const T x) {
        if(x<0) {
            putchar('-');
            write(-x);
            return;
        }
        if(x>9) write(x/10);
        putchar(x%10^48);
    }
    
    #include <cstring>
    
    const int maxn = 1e6+5;
    const int mod = 1e9+7;
    
    int n,a[maxn],dp[maxn][2],inv2,inv6;
    
    inline int adj(int x,int y) {
    	return x+y>=mod?x+y-mod:(x+y<0?x+y+mod:x+y); 
    }
    
    inline int inv(int x,int y=mod-2) {
    	int r=1;
    	while(y) {
    		if(y&1) r=1ll*r*x%mod;
    		x=1ll*x*x%mod; y>>=1;
    	}
    	return r;
    }
    
    int s1(int x) {
    	return 1ll*x*(x+1)%mod*inv2%mod;
    }
    
    int s2(int x) {
    	return 1ll*x*(x+1)%mod*(x*2+1)%mod*inv6%mod;
    }
    
    int DP(int O,int zero) { 
    	memset(dp,0,sizeof dp);
    	dp[1][0]=O,dp[1][1]=O^1;
    	for(int i=1;i<=n;++i) {
    		int to = i%n+1;
    		dp[to][0] = adj(dp[to][0],1ll*dp[i][0]*s1(a[i]-zero)%mod);
    		dp[to][0] = adj(dp[to][0],1ll*dp[i][1]*(a[i]-zero+1)%mod);
    		dp[to][1] = adj(dp[to][1],1ll*dp[i][0]*adj(1ll*a[i]*s1(a[i])%mod,-s2(a[i]))%mod);
    		dp[to][1] = adj(dp[to][1],1ll*dp[i][1]*s1(a[i])%mod);
    	}
    	return adj(O?dp[1][0]:dp[1][1],-1);
    }
    
    int main() {
    	n=read(9);
    	inv2 = inv(2), inv6 = inv(6);
    	for(int i=1;i<=n;++i)
    		a[i]=read(9);
    	print(adj(adj(DP(0,0),DP(1,0)),-adj(DP(0,1),DP(1,1))),'
    ');
    	return 0;
    }
    

    彩蛋

    还有一个 矩阵 的做法,但我真的懒得看了。咕咕咕…

  • 相关阅读:
    hdu4347 The Closest M Points(kdtree+stl)
    bzoj1941 [Sdoi2010]Hide and Seek
    bzoj1941 [Sdoi2010]Hide and Seek
    bzoj2648 SJY摆棋子(不带修改的KDtree的学习)
    bzoj2648 SJY摆棋子(不带修改的KDtree的学习)
    bzoj2588 Spoj 10628. Count on a tree
    hdu2665 Kth number(主席树模板)
    hdu2665 Kth number(主席树模板)
    luoguP3168 [CQOI2015]任务查询系统
    12.模板别名以及auto定义返回值
  • 原文地址:https://www.cnblogs.com/AWhiteWall/p/15416272.html
Copyright © 2020-2023  润新知