• 【未知来源】玩具谜题


    题意

      有 (n) 个数,你需要给每个数涂上红色或蓝色,使得任意两个红色的数不小于一个常数 (A),且任意两个蓝色的数不小于一个常数 (B)。求方案数。
      (nle 10^5)
      (1le A,B,a_ile 10^{18})
      (a_ilt a_{i+1})

    题解

    solution 1

      首先有个小学生都会写的 (30) 分暴力 ( ext{dp}):设 (dp(j,k)) 表示涂完前 (i) 个数后,最后一个红数在第 (j) 位,最后一个蓝数在第 (k) 位。
      显然状态中不用记 (i),因为 (max(j,k)=i)

    #include<bits/stdc++.h>
    #define ll long long
    #define N 2005
    #define mod 1000000007
    using namespace std;
    inline ll read(){
    	ll x=0; bool f=1; char c=getchar();
    	for(;!isdigit(c); c=getchar()) if(c=='-') f=0;
    	for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
    	if(f) return x;
    	return 0-x;
    }
    ll n,a,b,p[N];
    int dp[N][N];
    int main(){
    	n=read(), a=read(), b=read();
    	if(n>2000){puts("0"); return 0;}
    	for(int i=1; i<=n; ++i) p[i]=read();
    	dp[0][1]=dp[1][0]=1;
    	for(int i=2; i<=n; ++i){
    		if(i-1==0 || p[i]-p[i-1]>=a) for(int j=0; j<=i-2; ++j) (dp[i][j]+=dp[i-1][j])%=mod;
    		if(i-1==0 || p[i]-p[i-1]>=b) for(int j=0; j<=i-2; ++j) (dp[j][i]+=dp[j][i-1])%=mod;
    		for(int j=0; j<=i-2; ++j){
    			if(!j || p[i]-p[j]>=a) (dp[i][i-1]+=dp[j][i-1])%=mod;
    			if(!j || p[i]-p[j]>=b) (dp[i-1][i]+=dp[i-1][j])%=mod;
    		}
    	}
    	int ans=0;
    	for(int i=0; i<=n; ++i) (ans+=((dp[n][i]+dp[i][n])%mod))%=mod; 
    	cout<<ans<<endl;
    	return 0;
    }
    

      然后我们发现这个转移好像就是一堆数组平移。
      我们把 (dp(j,k)) 的矩阵画出来

      一种颜色圈的区间对应一个 (i)(j=i)(k=i) 对应两个不相交的一维数组。
      所以我们可以把 ( ext{dp}) 的两维拆成两个数组分开处理,一个数组满足 (j=i),然后下标表示 (k);一个数组满足 (k=i),然后下标表示 (j)
      考虑预处理出每个前缀 ([1,i]) 中最后一个满足 (a_i-a_rge A)(r) 和最后一个满足 (a_i-a_bge B)(b),则暴力代码可以改写为

      (query(x,y)) 表示求 (x) 数组第 (0)(y) 位的和。

      我们发现问题可以简化为支持两个数组的整体复制、求前缀和、前缀清零、尾部加数。
      主席树维护即可。复杂度 (O(nlog n))
      啊呸,前缀清零直接打 ( ext{tag}) 就行了,然后就可以求前缀和了。复杂度 (O(n))
      (其实细节很好写的,为啥我这么弱智想了半天)

    #include<bits/stdc++.h>
    #define ll long long
    #define N 100002
    #define mod 1000000007
    using namespace std;
    inline ll read(){
    	ll x=0; bool f=1; char c=getchar();
    	for(;!isdigit(c); c=getchar()) if(c=='-') f=0;
    	for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
    	if(f) return x;
    	return 0-x;
    }
    int n; ll A,B,x[N];
    int r[N],b[N],dp[2][N],sum[2][N],lst[2];
    int getSum(int c, int x){
    	if(x<lst[c]) return 0;
    	return ((sum[c][x]-sum[c][lst[c]-1])%mod+mod)%mod;
    }
    int main(){
    	n=read(), A=read(), B=read();
    	for(int i=1; i<=n; ++i) x[i]=read();
    	r[1]=b[1]=0;
    	for(int i=2; i<=n; ++i){
    		r[i]=r[i-1], b[i]=b[i-1];
    		while(x[i]-x[r[i]+1]>=A) ++r[i];
    		while(x[i]-x[b[i]+1]>=B) ++b[i];
    	}
    	dp[0][0]=dp[1][0]=1, sum[0][0]=sum[1][0]=1;
    	for(int i=2; i<=n; ++i){
    		(dp[0][i-1]+=getSum(1,min(r[i],i-2)))%=mod;
    		(dp[1][i-1]+=getSum(0,min(b[i],i-2)))%=mod;
    		if(r[i]!=i-1) lst[0]=i-1;
    		if(b[i]!=i-1) lst[1]=i-1;
    		sum[0][i-1]=(sum[0][i-2]+dp[0][i-1])%mod;
    		sum[1][i-1]=(sum[1][i-2]+dp[1][i-1])%mod;
    	}
    	//int ans=0;
    	//for(int i=lst[0]; i<=n; ++i) (ans+=dp[0][i])%=mod;
    	//for(int i=lst[1]; i<=n; ++i) (ans+=dp[1][i])%=mod;
    	cout<<(getSum(0,n-1)+getSum(1,n-1))%mod<<endl;
    	return 0;
    }
    

    solution 2

      一个与暴力无关的做法。
      考虑从大到小钦定每个数为红色还是蓝色。
      若 (a_n) 涂了红色,那么 (a_{n-1},a_{n-2},cdots) 等数就必须涂蓝色。

      比如有 (6) 个数:3 4 7 8 9 11
      (A=3,space B=4)
      如果把 (11) 涂成红色,那么 (8,9) 就必须涂成蓝色。
      而把 (8,9) 涂成蓝色,(7) 就必须涂成红色。
      但是 (7)(8) 都影响不到 (4) 的颜色。
      所以设 (dp(i,0/1)) 表示涂完前 (i) 个数的方案数,那么 (7,8,9,11) 这些数单独组成一个影响连通块,这一块对 (7) 以前的数的颜色没有任何影响,可以有 (dp(6)+=dp(2))

      现在我们要预处理出每个数最多往前影响多少位。
      设 (pos(i,0/1)) 表示第 (i) 个数涂红/蓝色,它往前最近的影响不到的数在哪一位。
      则 (pos) 可以递推,比如上例中,(pos(6,0)=pos(4,1)=pos(3,0)=2)
      于是用 (dp) 的递推式 (dp(i)=dp(pos(i,0/1))) 推出 (dp(n)) 即可。

      当然,这个做法需要特判无解的情况。
      比如上例中 (11) 涂成红色,(8,9) 就必须涂成蓝色,但 (8,9) 两个数差 (1)(B=4),所以不能同时涂成蓝色。这时 (pos(6,0)=-1)。最后计算 (dp(i)) 时跳过 (pos(i,j)=-1) 的情况。
      我们需要用 ( ext{ST}) 表、线段树等数据结构维护区间最小差分值。
      复杂度 (O(nlog n))

      code

  • 相关阅读:
    在Vue脚手架里面使用font-awsome
    在webstorm上使用git
    smartGit继续使用的方法
    工作笔记
    “老司机”传授给“小白”的职业经验
    兼容性问题(目前遇到的)
    web前端页面项目经验总结
    jquery中隐藏div的几种方法
    懒加载和预加载
    JS 中的事件绑定、事件监听、事件委托
  • 原文地址:https://www.cnblogs.com/scx2015noip-as-php/p/11519523.html
Copyright © 2020-2023  润新知