Educational Codeforces Round 137 (Rated for Div. 2)练习笔记
\(\text{A. Password}\)
\(\text{Monocarp}\) 的手机密码由 \(4\) 位数字组成(首位可以为 \(0\) ),其中只包含两个不同的数字,并且这两个数字都出现了两次。现在他告诉你他的密码肯定不会包含哪些数字,让你求出有多少种可能的密码。多组数据。
其实答案就是 \({10-n\choose 2}{4\choose 2}\) 。
int T, n;
int main(){
T = rd();
while(T--){
n = rd();
fp(i, 1, n)rd();
printf("%d\n", (10-n)*(9-n)*3);
}
return 0;
}
\(\text{B. Permutation Value}\)
定义一个排列的价值为:有多少个区间,使得这个区间也是一个排列。给定一个 \(n(n\leq 50)\) ,要求输出任意一个价值最小的长度为 \(n\) 的排列。多组数据。
如果一个区间也是排列,并且长度 \(>1\) ,那么它肯定包含 \(1,2\) 。所以只需要让 \(1\) 在最左边, \(2\) 在最右边,就可以使这个排列的价值最小。
int T, n;
int main(){
T = rd();
while(T--){
n = rd();
printf("1 "); fp(i, 3, n)printf("%d ", i); printf("2\n");
}
return 0;
}
\(\text{C. Save the Magazines}\)
从左到右排列了 \(n(n\leq 2\times 10^5)\) 个盒子,第 \(i\) 个盒子里有 \(a_i\) 本杂志。有的盒子上有一个盖子,有的则没有。一次操作为将某个盒子上的盖子移到左边的盒子上,每个盖子最多只能被移动一次。求若干次操作之后,最多有多少本杂志可以被盖子盖住。多组数据。
很好想的做法是直接 \(\text{dp}\) 。设 \(f_{i,0}\) 表示不移动第 \(i\) 个盒子上的盖子,第 \(1\)~\(i\) 个盒子最多可以有多少本杂志被盖住,设 \(f_{i,1}\) 表示移动第 \(i\) 个盒子上的盖子(如果有的话),第 \(1\)~\(i\) 个盒子最多可以有多少本杂志被盖住。
如果第 \(i\) 个盒子一开始没有盖子,则 \(f_{i,0} = \max\{f_{i-1,0},f_{i-1,1}\}\) ;
否则, \(f_{i,0} = \max\{f_{i-1,0},f_{i-1,1}\}+a_i,f_{i,1}=f_{i-1,lid_{i-1}}+a_{i-1}\) ,其中 \(lid_i\) 表示第 \(i\) 个盒子上一开始有没有盖子。
cint maxn = 200010;
int T, n, lid[maxn], a[maxn];
unsigned int f[maxn][2];
int main(){
T = rd();
while(T--){
n = rd();
fp(i, 1, n)scanf("%1d", &lid[i]);
fp(i, 1, n)a[i] = rd(), f[i][0] = f[i][1] = 0;
if(lid[1])f[1][0] = a[1];
fp(i, 2, n){
if(!lid[i])f[i][0] = max(f[i-1][0], f[i-1][1]);
else{
f[i][0] = max(f[i-1][0], f[i-1][1])+a[i];
f[i][1] = (lid[i-1] ? f[i-1][1] : f[i-1][0]) + a[i-1];
}
}
printf("%u\n", max(f[n][0], f[n][1]));
}
return 0;
}
\(\text{D. Problem with Random Tests}\)
给定一个长度为 \(n(n\leq 10^6)\) 的 \(01\) 串,要求截取任意两段(可以相交),使得这两段或运算结果最大。数据随机。
或运算具有贪心的性质,所以让第一段取到最左边的 \(1\) 和 \(01\) 串的右端点显然是最优的。
然后考虑一种暴力,枚举第二段的左端点,然后暴力算,复杂度是 \(\text O(n^2)\) 的。
考虑第一个优化,首先最优的方法肯定是选取与第一个 \(1\) 在同一块(全是 \(1\) 的一段)里的 \(1\) 作为第二段的左端点,因为这样才可能通过调整右端点的位置让这个 \(1\) 与第一段的第一个 \(0\) 进行或运算。
这样只是常数上进行了优化。再考虑第二个优化,怎么使用随机数据这个条件?
我们将使用第一个优化过后所有的 \(1\) 作为备选项,并将它们与第一段的第一个 \(0\) 对齐。然后从左到右枚举第一段里的所有 \(0\) ,看每个备选的第二段能否在对应的位置上是 \(1\) 。如果有至少一个备选项满足条件,则将所有不满足条件的备选项去掉;否则全部保留,继续往后枚举第一段的 \(0\) 。最后剩下的就是最优的第二段。
因为数据随机,所以每次都期望去掉一半的备选项,所以这样做的复杂度是 \(\text O(n\log n)\) 的。
cint maxn = 1000010;
int n, a[maxn], pos[maxn], cnt, f[maxn], tot;
int main(){
n = rd();
fp(i, 1, n)scanf("%1d", &a[i]);
int now = 0; fp(i, 1, n)if(a[i]){ now = i; break; }
if(!now)return putchar('0'), 0;
fp(i, now+1, n)if(!a[i])pos[++cnt] = i;
fp(i, 1, pos[1]-1)if(a[i])f[++tot] = i;
fp(i, 1, cnt){
int fl = 0, _tot = 0;
fp(j, 1, tot)if(f[j] + pos[i]-pos[1] <= n && a[f[j] + pos[i]-pos[1]]){ fl = 1; break; }
if(!fl)continue;
fp(j, 1, tot)if(f[j] + pos[i]-pos[1] <= n && a[f[j] + pos[i]-pos[1]])f[++_tot] = f[j];
tot = _tot;
}
int tmp = 0;
fp(i, now, n){
if(a[i])putchar('1');
else{
++tmp;
putchar(a[f[1]+pos[tmp]-pos[1]] ? '1' : '0');
}
}
return 0;
}
\(\text{E. FTL}\)
有两门激光炮,各自有一个威力 \(d_1,d_2(d_i\leq 5000)\) 和充能时间 \(p_1,p_2(p_i\leq 10^{12})\) 。敌人拥有 \(h(h\leq 5000)\) 滴血和 \(s(s\leq \min\{d_1,d_2\})\) 点防御,每次在某一门激光炮充能完毕后使用它,也可以等两门激光炮都充能后一起发射,造成的伤害为:这次使用的激光炮的威力(或威力之和)\(-s\) 。求出最少需要多少时间可以击杀敌人。
由于两门炮分别充能,所以贪心是不好做的……
考虑 \(\text{dp}\) 。设 \(dp_i\) 代表造成 \(i\) 点伤害最少需要多长时间。我们可以钦定最后一次攻击为两门炮一起攻击,因为如果不是一起攻击的,我们可以让较早发射的炮等到另一门炮充能完毕过后一起发射,而且这样造成的伤害更高。那我们可以枚举第一门炮单独发射多少次过后进行一次两门炮齐射,再枚举第二门炮单独发射多少次过后进行两门炮齐射。复杂度大概就是 \(\text{O(h^2)}\) 的。
cint maxn = 5010;
int p1, p2, h, s;
LL t1, t2, f[maxn];
int main(){
scanf("%d %lld", &p1, &t1);
scanf("%d %lld", &p2, &t2);
scanf("%d %d", &h, &s);
memset(f, 0x3f, sizeof f), f[0] = 0;
fp(i, 0, h){
f[min(h, i+p1-s)] = min(f[min(h, i+p1-s)], f[i]+t1);
f[min(h, i+p2-s)] = min(f[min(h, i+p2-s)], f[i]+t2);
fp(j, 1, h){
if(j*t1 >= t2){
LL dam = 1ll*(j-1)*(p1-s) + (j*t1-t2)/t2*(p2-s) + p1+p2-s;
f[min((LL)h, i+dam)] = min(f[min((LL)h, i+dam)], f[i]+j*t1);
}
if(j*t2 >= t1){
LL dam = 1ll*(j-1)*(p2-s) + (j*t2-t1)/t1*(p1-s) + p1+p2-s;
f[min((LL)h, i+dam)] = min(f[min((LL)h, i+dam)], f[i]+j*t2);
}
}
}
printf("%lld\n", f[h]);
return 0;
}
\(\text{F. Intersection and Union}\)
给定 \(n(n\leq 3\times 10^5)\) 个区间 \([l_i,r_i](l_i,r_i\leq 3\times 10^5)\) 。区间之间的操作有三种, \(\bigcup,\bigcap,\bigoplus\) ,分别表示交、并、对称差(并集减去交集)。记第 \(i\) 个区间为 \(S_i\) ,定义一个操作序列 \(\{op_1,op_2,...,op_{n-1}\}\) 的价值为 \(|(((S_1op_1S_2)op_2S_3)op_3S_4)...op_{n-1}S_n|\) 。求出所有操作序列的价值之和,对 \(998244353\) 取模。
考虑单独算每个位置对答案的贡献,即计算有多少种操作序列能使它留在最后的集合里。对于位置 \(x\) ,设 \(f_{i,0}\) 表示进行了第 \(i-1\) 次操作后,\(x\) 不在当前集合里的方案数; \(f_{i,1}\) 表示在当前集合里的方案数。
如果 \(x\in[l_i,r_i]\) ,分别考虑三种操作,那么有 \(f_{i,0} = f_{i-1,0}+f_{i-1,1},f_{i,1} = (f_{i-1,0}+f_{i-1,1})+f_{i-1,1}+f_{i-1,0}\) 。
如果 \(x\notin[l_i,r_i]\) ,那么有\(f_{i,0}=f_{i-1,0}+(f_{i-1,0}+f_{i-1,1})+f_{i-1,0},f_{i,1} = f_{i-1,1}+f_{i-1,1}\) 。
那么直接这样暴力算就是 \(\text O(n^2)\) 的。
可以发现当 \(x\in[l_i,r_i]\) 时 \([f_{i,0},f_{i,1}] = [f_{i-1,0},f_{i-1,1}]\times \left[ \begin{array}{cc|r} 1 & 2\\1&2 \end{array}\right]\);
当 \(x\notin[l_i,r_i]\) 时有 \([f_{i,0},f_{i,1}] = [f_{i-1,0},f_{i-1,1}]\times \left[ \begin{array}{cc|r} 3 & 0\\1&2 \end{array}\right]\) 。
所以我们可以把所有区间的端点进行排序,并且从左到右枚举 \(x\) ,如果 \(x=l_i\) 或者 \(x=r_i+1\),则更新第 \(i\) 个区间的转移矩阵,用线段树维护一个全局积,即可做到 \(\text O(n\log n)\) 。
cint maxn = 300010, mod = 998244353;
int n, l[maxn], r[maxn], L, R, f[maxn][2], ans;
int p1[maxn], p2[maxn];
il bool cmp1(cint &x, cint &y){ return l[x] < l[y]; }
il bool cmp2(cint &x, cint &y){ return r[x] < r[y]; }
struct matrix{ int a[3][3]; }t[maxn<<2], m1, m2;
il matrix operator * (cn matrix &x, cn matrix &y){
matrix ans;
ans.a[1][1] = (1ll*x.a[1][1]*y.a[1][1] + 1ll*x.a[1][2]*y.a[2][1])%mod;
ans.a[1][2] = (1ll*x.a[1][1]*y.a[1][2] + 1ll*x.a[1][2]*y.a[2][2])%mod;
ans.a[2][1] = (1ll*x.a[2][1]*y.a[1][1] + 1ll*x.a[2][2]*y.a[2][1])%mod;
ans.a[2][2] = (1ll*x.a[2][1]*y.a[1][2] + 1ll*x.a[2][2]*y.a[2][2])%mod;
return ans;
}
void build(int d, int l, int r){
if(l == r)return t[d] = m2, void();
int md = l+r>>1;
build(d<<1, l, md), build(d<<1|1, md+1, r);
t[d] = t[d<<1]*t[d<<1|1];
}
void mdf(int d, int l, int r, cint &des, cn matrix &m){
if(des == 1)return;
if(l == r)return t[d] = m, void();
int md = l+r>>1;
if(des <= md)mdf(d<<1, l, md, des, m);
else mdf(d<<1|1, md+1, r, des, m);
t[d] = t[d<<1]*t[d<<1|1];
}
int main(){
n = rd(), L = 3e5;
fp(i, 1, n)l[i] = rd(), r[i] = rd();
fp(i, 1, n)if(l[i] < L)L = l[i];
fp(i, 1, n)if(r[i] > R)R = r[i];
fp(i, 1, n)p1[i] = p2[i] = i;
sort(p1+1, p1+1+n, cmp1), sort(p2+1, p2+1+n, cmp2);
m1.a[1][1] = m1.a[2][1] = 1, m1.a[1][2] = m1.a[2][2] = 2;
m2.a[1][1] = 3, m2.a[1][2] = 0, m2.a[2][1] = 1, m2.a[2][2] = 2;
build(1, 2, n);
int now1 = 0, now2 = 0;
fp(i, L, R){
while(now1 < n && l[p1[now1+1]] == i)++now1, mdf(1, 2, n, p1[now1], m1);
while(now2 < n && r[p2[now2+1]] == i-1)++now2, mdf(1, 2, n, p2[now2], m2);
if(l[1] <= i && i <= r[1])ans = (ans + t[1].a[2][2])%mod;
else ans = (ans + t[1].a[1][2])%mod;
}
printf("%d\n", ans);
return 0;
}
\(\text{G. Antifibonacci Cut}\)
定义 \(01\) 串 \(f\) 为斐波那契串,其中 \(f_1=0,f_2 = 1,f_{n}=f_{n-1}+f_{n-2}\) ,其中 \(+\) 代表拼接。再定义 \(g(s)\) 表示把 \(s\) 分割成若干个子串,并且没有任何子串是斐波那契串的方案数。现给出 \(n(n\leq 3000)\) 个 \(01\) 串 \(s_1,s_2,...,s_n\) ( \(|s_i|\leq 1000\) ),要求对于每个 \(i\) ,计算 \(g(s_1+s_2+...+s_i)\pmod {998244353}\) 。内存限制为 \(4\text M\)。
首先可以做的事情是把所有串都拼起来,然后考虑怎么 \(\text{dp}\) 。
一种比较暴力的 \(\text{dp}\) 为,设 \(dp_i\) 表示 \(g(s[1...i])\) 。若没有非斐波那契串的限制,那么 \(dp_i=\sum_{j=1}^{i-1}dp_j\) 。可以发现的一条性质为, \(f_{i-2}\) 是 \(f_i\) 的后缀,并且 \(f_{33}\) 的长度会大于 \(3\times 10^6\) 。所以我们可以用 \(\text{exKMP}\) 求出 \(s[1...i]\) 分别与 \(f_{32},f_{31}\) 的 \(\text{LCS}\) 。以 \(f_{32}\) 为例,假设它的 \(\text{LCS}\) 对应的是 \(f_k\) ,那么令 \(dp_i\)-=\(\sum_{j=1}^kdp_{i-|f_j|}\) 即可。复杂度是 \(\text O((\sum s_i)\log)\) 。
然而内存限制使得这题连 \(10^6\) 的数组都开不下……
可以发现另外一些性质(默认不考虑 \(f_1\) ): \(f_{i-1}\) 是 \(f_i\) 的前缀;任何一个斐波那契串的前缀都可以表示成若干个编号不连续的斐波那契串拼接而成的形式。
下证第二条性质:首先长度为 \(1\) 的前缀就是 \(f_2=1\) ,长度为 \(2\) 的前缀就是 \(f_3=10\) 。假设长度为 \(1\)~\(n-1\) 的前缀都满足这个性质,设 \(f_k\) 为最长的长度小于等于 \(n\) 的斐波那契串。若 \(|f_k|=n\) ,那么长度为 \(n\) 时就成立。否则,由于 \(s[f_k+1...n]\) 必然是 \(f_{k-1}\) 的前缀,且长度必然小于 \(|f_{k-1}|\) ,所以能表示成若干个编号不连续的斐波那契串的拼接,且编号最大的串编号不超过 \(k-2\) 。
为了求出斐波那契串长度为 \(n\) 的前缀由哪些斐波那契串拼接而成,可以参考下面的代码:
fb(i, 32, 1)if(n > fib[i])n -= fib[i];
有了这些性质之后,假设现在计算了 \(dp_{i-1}\) ,我们可以维护 \(s[1...i-1]\) 有哪些长度的斐波那契串前缀的后缀,于是得到若干个二元组: \((l,dp_{i-l})\) 。考虑计算 \(dp_i\) 时怎么维护这些二元组。首先,如果 \(s_i=1\) ,此时就会得到一个新的二元组 \((1,dp_{i-1})\) 。枚举旧的二元组,假设枚举到 \((l,v)\) ,讨论长度为 \(l\) 的斐波那契串前缀是否能拓展 \(s_i\) 这一位。利用第二条性质,我们可以得出一个长度为 \(l+1\) 的斐波那契串前缀最后拼接的斐波那契串是否为 \(1\) 。如果是的话,那么 \(s_i\) 就必须为 \(1\) ,否则必须为 \(0\) 。如果可以拓展这一位,那么检查 \(l+1\) 是否为斐波那契数,如果是,那么就让 \(dp_i\)-=\(v\) ( \(dp_i\) 初始为 \(\sum_{j=1}^{i-1}dp_j\) )。那么复杂度是小于 \(\text O(32\sum_{i=1}^n|s_i|)\) 的。
cint maxn = 1010, mod = 998244353;
int n, len, fib[33], l[33], f[33], nl[33], nf[33], tot, ans = 1, sum = 1;
char s[maxn];
map<int, int> vis;
il int getnxt(int len){
fb(i, 32, 2)if(len > fib[i])len -= fib[i];
return len == 1;
}
il void calc(int c){
int nans = (sum-ans+mod)%mod, ntot = 0;
if(c)ntot = 1, nl[ntot] = 1, nf[ntot] = ans;
fp(i, 1, tot){
if(c != getnxt(l[i]+1))continue;
if(vis.count(l[i]+1))nans = (nans-f[i]+mod)%mod;
nl[++ntot] = l[i]+1, nf[ntot] = f[i];
}
ans = nans, sum = (sum+ans)%mod, tot = ntot;
fp(i, 1, tot)f[i] = nf[i], l[i] = nl[i];
}
int main(){
fib[0] = fib[1] =1, vis[1] = 1;
fp(i, 2, 32)fib[i] = fib[i-1]+fib[i-2], vis[fib[i]] = 1;
scanf("%d", &n);
while(n--){
scanf("%s", s+1), len = strlen(s+1);
fp(i, 1, len)calc(s[i]-'0');
printf("%d\n", ans);
}
return 0;
}