这场一共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,2m−1]划分成 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 Li≤k≤Ri,1≤j≤n, 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(ai−1,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[i−1]里合法的 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[i−1]的状态进行转移,详见代码注释。
边界: d p [ 0 ] [ 0 ] [ m ] = 1 dp[0][0][m]=1 dp[0][0][m]=1,表示一个和下一组无关的初始值。
总结:
- 动态规划的状态设计:转移依赖于状态,又和状态不是直接相连,这样的dp以前还没有做过。
- wa点:多测清空,变量写错。
- 还是要多练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;
}