首页 > 其他分享 >P9400 「DBOI」Round 1 三班不一般 做题笔记

P9400 「DBOI」Round 1 三班不一般 做题笔记

时间:2023-06-24 09:11:30浏览次数:42  
标签:P9400 return 200005 int DBOI times 刺眼 Round mod

题目链接

最近搬运一些洛谷上的题解到这里来,一是增加我的博文数量,二是缓解一下我的博客园冷清的气氛。

我的做法和题解里的做法不一样,麻烦了许多。

首先看到连续的几盏灯刺眼就不行了,当然能够想到动态规划,设 $f[i][j]$ 为看到第 $i$ 个宿舍,末尾有连续 $j$ 个灯刺眼,且前面的灯都合法的方案数。

当前这盏灯,可以刺眼也可以不刺眼,刺眼:

$f[i][j]=f[i - 1][j - 1]\times\text{当前灯刺眼的方案数}$。

不刺眼的话,前面再多连续的刺眼都废了,所以:

$f[i][0] = (f[i - 1][1] + f[i - 2][2] + \cdots + f[i - 1][a - 1]) \times \text{当前灯不刺眼的方案数}$。

然后就能得到 $O(n^2)$ 的做法了:

#include <iostream>
#define int long long
using namespace std;
int n, a, b;
int l[200005], r[200005];
int f[1005][1005];//前 i 盏,末尾有连续 j 盏刺眼 
const int mod = 998244353;
int fun (int x, int y, int z) {
    if (y <= z) return 0;
    if (x <= z) return y - z;
    return y - x + 1;
}
int fun_ (int x, int y, int z) {//x 到 y 中小于等于 z 的数的个数 
    if (x > z) return 0;
    return min (y, z) - x + 1;
}
signed main () {
    cin >> n >> a >> b;
    for (int i = 1; i <= n; i ++) cin >> l[i] >> r[i];
    if (a == n + 1) {
        long long ans = 1;
        for (int i = 1; i <= n; i ++) {
            ans *= long (r[i] - l[i] + 1);
            ans %= mod;
        }
        cout << ans << "\n";
    } else if (a == 1) {
        long long ans = 1;
        for (int i = 1; i <= n; i ++) {
            if (l[i] > b) ans = 0;
            else ans *= long (min (r[i], b) - l[i] + 1);
            ans %= mod;
        }
        cout << ans << "\n";
    } else {
        long long ans = 0;
        f[0][0] = 1;
        for (int i = 1; i <= n; i ++) {
            for (int j = 1; j < a; j ++) {
                f[i][j] = f[i - 1][j - 1] * fun (l[i], r[i], b) % mod;
            }
            for (int j = 0; j < a; j ++) {
                f[i][0] += f[i - 1][j];
                if (f[i][0] > mod) f[i][0] -= mod;
            }
            f[i][0] = f[i][0] * fun_ (l[i], r[i], b) % mod;
        }
        for (int i = 0; i < a; i ++) ans = (ans + dp[n][i]) % mod;
        cout << ans << "\n";
    }
    return 0;
}

接着来考虑怎么优化呢?我试了半天,这个东西不能用斜率,不能用四边形不等式,好像没啥好优化的,这时就要转换状态了。

根据正难则反的经典套路,我们再设 $F[i]$ 为到第 $i$ 个宿舍,刺眼的方案数,就发现可以了。

为了不重复的统计每一个刺眼的方案,如果一个方案有多个连续的 $a$ 盏刺眼的灯,它只会被第一个连续 $a$ 个刺眼的灯来统计。

所以:$F[i] = F[i - 1] \times \text{当前这个灯乱填(前面已经不满足了加上这个也不满足)}$

$+\text{最近 a 个全部刺眼的方案数}\times \text{第 i - a 个不刺眼的方案数 (或者第 i - a 个是 0 号宿舍)}\times \text{(i - a - 1 以及再之前的灯都不刺眼的方案数)}$。

乱搞的方案数显然是 $r[i] - l[i] + 1$,后面呢?

最近 $a$ 个灯全部刺眼和第 $a - 1$ 个灯不刺眼也好弄,而 $i-a-1$ 以及前面的灯都不刺眼的方案数,其实就是 $f[i - a - 1]$。

$f$ 怎么求呢?根据刚刚的正难则反,显然 $f$ 等于全部乱填减去不合法的方案数。

状态转移方程:

$f[i] = luangao[i] - F[i]$

$F[i] = F[i - 1] \times (r[i] - l[i] + 1) + ciyan[i] \times inv (ciyan[i - a] \times buciyan (l[i - a], r[i - a], a) \times f[i - a - 1])$,其中 $buciyan (l, r, k)$ 求的是 $l$ $r$ 中有多少数小于等于 $k$。

其实把 $f$ 的方程带回 $F$ 更简单了,可以自己试下。

初始化细节很多,大家仔细看下。状态转移方程较麻烦,不过代码还行:

#include <iostream>
#define int long long
using namespace std;
int n, a, b;
int l[200005], r[200005];
int f[200005], F[200005];
int luangao[200005];
int ciyanfangan[200005], zero[200005];
const int mod = 998244353;
int ciyande (int x, int y, int z) {
    if (y <= z) return 0;
    if (x <= z) return y - z;
    return y - x + 1;
}
int buciyande (int x, int y, int z) {
    if (x > z) return 0;
    return min (y, z) - x + 1;
}
int q_pow (int x, int y) {
    if (y == 0) return 1;
    if (y & 1) return x * q_pow (x * x % mod, y >> 1) % mod;
    return q_pow (x * x % mod, y >> 1);
}
int inv (int x) {return q_pow (x, mod - 2);}
signed main () {
    cin >> n >> a >> b;
    luangao[0] = 1;
    for (int i = 1; i <= n; i ++) {
        cin >> l[i] >> r[i];
        luangao[i] = luangao[i - 1] * (r[i] - l[i] + 1) % mod;
        if (ciyande (l[i], r[i], b) == 0) zero[i] = 1;
        zero[i] += zero[i - 1];
    }
    for (int i = 1; i + a - 1 <= n; i ++) {
        if (zero[i + a - 1] - zero[i - 1] != 0) continue;
        if (ciyanfangan[i - 1] != 0) {
            ciyanfangan[i] = ciyanfangan[i - 1] * inv (ciyande (l[i - 1], r[i - 1], b) ) % mod * ciyande (l[i + a - 1], r[i + a - 1], b) % mod;
            continue;
        }
        ciyanfangan[i] = 1;
        for (int j = i; j <= i + a - 1; j ++) ciyanfangan[i] = ciyanfangan[i] * ciyande (l[j], r[j], b) % mod;
    }
    for (int i = 0; i < a; i ++) f[i] = luangao[i];
    f[0] = 1;
    F[a] = ciyanfangan[1];
    f[a] = ( (luangao[a] - F[a]) % mod + mod) % mod;
    for (int i = a + 1; i <= n; i ++) {
        F[i] = (F[i - 1] * (r[i] - l[i] + 1) % mod + f[i - a - 1] * ciyanfangan[i - a + 1] % mod * buciyande (l[i - a], r[i - a], b) % mod);
        f[i] = ( (luangao[i] - F[i]) % mod + mod) % mod;
    }
    cout << f[n] << "\n";
    return 0;
}

 

标签:P9400,return,200005,int,DBOI,times,刺眼,Round,mod
From: https://www.cnblogs.com/Xy-top/p/17500667.html

相关文章

  • Codeforces Round 781 (Div. 2) E. MinimizOR (可持久化字典树)
    传送门题目大意:  T组测试数据每组测试数据先输入一个n表示有一个长度为n的一维数组,然后输入n个数字表示这个一维数组。紧接着输入一个k表示有k个询问,对于每个询问会输入一个l和一个r表示询问数组中[l,r]这个区间里面任意两个下标不重复的元素最小的或(|)是多少。解题思路: ......
  • Codeforces Round 881 (Div
    E.TrackingSegments给定初始长度为n,且全为0的序列a,然后给出m个线段,如果一个线段中1的个数严格大于0的个数,那么该线段称为一个漂亮线段,现在给出q次操作,每次操作使得序列a中位置x上的0变为1,请你求出第一次使得所有线段中出现漂亮线段的询问题解:二分答案容易发现答案具有单......
  • Codeforces Round 766 (Div. 2) 比赛报告
    0比赛经过比赛还没开始的时候就感觉状态不太好。果然。总归到底都是一个心态问题。A题经过看A题,结果半天看不懂,一开始没有注意到一定要在黑格子上操作。扔到DeepL上翻译了一下,再手玩一下样例就做出来了,速度有点慢。CF怎么这么喜欢出分讨题啊。看题目不能太急,要一个一......
  • Codeforces Round 881 (Div. 3)--F2
    F2.OmskMetro(hardversion)#include<bits/stdc++.h>usingnamespacestd;typedeflonglongll;#defineendl"\n"#defineintlonglongconstintN=2e5+5;constintINF=0x3f3f3f3f;//假设一个区间的最大字段和为max最小字段和为min//那么[min,max]区间的......
  • Codeforces Round 878 (Div. 3) D. Wooden Toy Festival
    题目翻译:给定一个序列,你可以把序列分为任意的三组不要求顺序,对于每一组序列给出一个数字作为标准,求出序列中和该数字的差绝对值的最大值,现在要求你选顶三个数字使得三个序列的差最大值的最大值最小解题思路:二分,要想方差最小,就让每一组的极差都最小,即最大值减最小值最小#include......
  • 洛谷 P8264 [Ynoi Easy Round 2020] TEST_100
    题目Link我们不妨来考虑所有询问都是\(l=1,r=n\)的情形,这种情况下需要对每个值处理出他经过一系列变换后变成了什么数。考虑用\(\text{solve}(p,l,r)\)表示我们现在要计算\(x\in[l,r]\)的这些数在经过\(x\leftarrow|x-a_p|,x\leftarrow|x-a_{p+1}|\),一直到\(x\leftar......
  • 练习记录-cf-Codeforces Round 881 (Div. 3)A-F1
    E是补的太蠢了没想到期末考完的复健A.SashaandArrayColoring题意:可以给不同数字涂上很多颜色,每个颜色的贡献是同一个颜色内的数字最大值和最小值的差思路:排序一遍,取头和尾的差#include<bits/stdc++.h>#defineclosestd::ios::sync_with_stdio(false),cin.tie(0)......
  • 使用flutter_background_service创建后台服务
    介绍flutter_background_service,它是一个在Flutter应用中创建和管理后台服务的库,并提供了一种简单的方式来执行长时间运行的任务。使用方法下面是关于flutter_background_service的使用方法的详细介绍:1、创建服务使用flutter_background_service库,你可以创建一个后台服......
  • Codeforces Round 880 (Div. 2) C. k-th equality
    看好久题目了,题目大意是给定三个位数A,B,C和一个k,要求求所有满足要求的a+b=c等式中的第k个等式等式按字典序由小到大枚举,例如1+9=10和2+6=8中1+9=10比2+6=8小思路我们首先求出a,b,c的取值范围,然后先确定a,对于每一个确定的a都有一个确定的b和c区间与之对应,并且a+b=c,c=a-b,当a和b......
  • Educational Codeforces Round 82 (Rated for Div. 2)_A. Erasing Zeroes(C++_模拟)
    Youaregivenastring.Eachcharacteriseither0or1.Youwantall1’sinthestringtoformacontiguoussubsegment.Forexample,ifthestringis0,1,00111or01111100,thenall1’sformacontiguoussubsegment,andifthestringis0101,100001o......