【题解】2019CCPC哈尔滨B Binary Numbers 动态规划,二进制,状态设计

这场一共272个人,有三道金牌题,A(22/340),B(29/198),L(27/388)。

做出任意两道就可以7题拿金,3道全出最高可以到第五名,不算打星第三名(渡渡鸟nb!)。

训练赛中我花了不少时间想B题,临下场才有一些不成熟的思路。

czq想出了L题但是写残了,整理一番之后我上去重写过了。

tyx和czq想出了A题,tyx写炸了(捶地笑)。

还要加油啊。


交题链接:GYM102394B. Binary Numbers

给定 m ( 17 ) m(17) m(17) n ( 2 m ) n(2^m) n(2m),然后把 [ 0 , 2 m − 1 ] [0,2^m-1] [0,2m1]划分成 N N N个不相交的数字区间 [ L i , R i ] [L_i,R_i] [Li,Ri]

从每个区间里选出一个代表数字 A i A_i Ai,如果对于所有的区间 i i i,满足

F ( A i , k ) ≥ F ( A j , k ) F(A_i,k)\ge F(A_j,k) F(Ai,k)F(Aj,k),其中 L i ≤ k ≤ R i , 1 ≤ j ≤ n L_i\le k\le R_i,1\le j\le n LikRi1jn F ( a , b ) F(a,b) F(a,b)表示把 a a a b b b都写成 m m m位的二进制字符串时,它们的最长公共前缀长度。

那么称这是一种合法的选择,它的贡献是 Π i = 1 n A i \Pi_{i=1}^n A_i Πi=1nAi

求所有不同合法选择的贡献之和,答案模100 000 007.


区间内一个点和区间所有值的lcp,一定在边界处取得最小值(可以画出01trie,按lca理解)。

所以对于每一个区间,只需要满足如下限制:

L C P ( a i , l i ) ≥ L C P ( a i − 1 , l i ) LCP(a_i,l_i)\ge LCP(a_{i-1},l_i) LCP(ai,li)LCP(ai1,li)

L C P ( a i , r i ) ≥ L C P ( a i + 1 , r i ) LCP(a_i,r_i)\ge LCP(a_{i+1},r_i) LCP(ai,ri)LCP(ai+1,ri)

考虑dp:
f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示考虑了前 i i i组, l c p ( a i , l i + 1 ) = j , l c p ( a i , r i ) = k lcp(a_i,l_{i+1})=j,lcp(a_i,r_i)=k lcp(ai,li+1)=j,lcp(ai,ri)=k时的贡献之和,枚举第 i i i组选择哪个数作为 a i a_i ai,那么暴力枚举 f [ i − 1 ] f[i-1] f[i1]里合法的 j j j k k k进行转移即可。

考虑dp:

f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示考虑完前 i i i组, l c p ( a i , l i + 1 ) = j , l c p ( a i , r i ) = k lcp(a_i,l_{i+1})=j, lcp(a_i,r_i)=k lcp(ai,li+1)=j,lcp(ai,ri)=k时的贡献之和。

枚举 a i a_i ai a i a_i ai会对一个固定的状态产生贡献,并且可以从一些 d p [ i − 1 ] dp[i-1] dp[i1]的状态进行转移,详见代码注释。

边界: d p [ 0 ] [ 0 ] [ m ] = 1 dp[0][0][m]=1 dp[0][0][m]=1,表示一个和下一组无关的初始值。


总结:

  1. 动态规划的状态设计:转移依赖于状态,又和状态不是直接相连,这样的dp以前还没有做过。
  2. wa点:多测清空,变量写错。
  3. 还是要多练dp,金牌题水平的dp是非常有挑战的。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int N=20, M = 131100,  MOD = 100000007;

/*
合法情况:和本组左侧>=上一组的a和本组左侧
和本组右侧>=下一组的a和本组右侧
*/

int m;
//计算m位下a和b的lcp
int cal(int a, int b)
{
	int res = 0;
	for(int i=m-1; i>=0; --i)
	{
		if((a>>i&1)==(b>>i&1)) ++res;
		else break;
	}
	return res;
}
//计算完前i组,和下一组左侧的lcp是j,和本组右侧的lcp是k的方案数

int dp[M][N][N], l[M], r[M]; 
int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

	int T = read();
	while(T--)
	{
		m = read(); int n = read();
		memset(dp, 0, (n+1)*sizeof(dp[0]));
		for(int i=1; i<=n; ++i)
			l[i] = read(), r[i] = read();
		dp[0][0][m] = 1; //和下一组无关,和本组强相关,即万能
		for(int i=1; i<=n; ++i)
		{
			for(int a=l[i]; a<=r[i]; ++a) //枚举本组代表元素a
			{
				int nowl = cal(a, l[i]); //和本组左侧的lcp
				int lstr = i>1 ? cal(a, r[i-1]) : 0; //和上一组右侧的lcp

				int nxtl = i<n ? cal(a, l[i+1]) : 0; //和下一组左侧的lcp
				int nowr = cal(a, r[i]); //和本组右侧的lcp
				//可以从哪些状态转移?f[i-1][<=nowl][>=lstr]
				//这个a会产生贡献的状态是f[i][nxtl][nowr]
				//产生多少贡献是之前的状态之和*a
				int sum = 0;
				for(int j=nowl; j>=0; --j)
					for(int k=lstr; k<=m; ++k)
						sum = (sum+dp[i-1][j][k])%MOD;
				dp[i][nxtl][nowr] = (dp[i][nxtl][nowr] + 1ll * sum * a) % MOD;
			}
		}
		int ans = 0;
		for(int j=0; j<=m; ++j)
			for(int k=0; k<=m; ++k)
				ans = (ans+dp[n][j][k])%MOD;
		printf("%d\n",ans );
	}

    return 0;
}


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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值