欢迎使用皮肤 Geek|

Tsawke

园龄:2年7个月粉丝:6关注:2

杂题小记(2023.02.25)

杂题小记(2023.02.25)

更好的阅读体验戳此进入

PJudge-21739 A. 【NOIP Round #5】青鱼和序列

题面

给定初始序列,存在两种操作,将序列复制并接在前面,将序列复制并翻转并接在前面,需要在 m 次操作后最大化:

i=1nj=1iajmod(1×109+7)

Solution

“容易” 发现翻转多次与翻转一次等效,“容易” 发现一次的翻转在任意位置都等效,于是模拟跑一下即可。

复杂度最优可以优化到 O(n+logm),本题限制十分宽松故写的很丑也没事。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define MOD (ll)(1e9 + 7)

template < typename T = int >
inline T read(void);

int N, M;
ll A[110000];
ll sum[110000], rsum[110000], ssum, srsum;
ll pow2s[110000], pow2[110000];
ll spow2s[110000];

int main(){
    N = read(), M = read();
    for(int i = 1; i <= N; ++i)A[i] = read();
    for(int i = 1; i <= N; ++i)sum[i] = (sum[i - 1] + A[i]) % MOD;
    for(int i = N; i >= 1; --i)rsum[i] = (rsum[i + 1] + A[i]) % MOD;
    for(int i = 1; i <= N; ++i)(ssum += sum[i]) %= MOD, (srsum += rsum[i]) %= MOD;
    ll base(N);
    for(int i = 1; i <= M - 1; ++i){
        ssum = (ssum + ssum + sum[N] * base % MOD) % MOD;
        srsum = (srsum + srsum + sum[N] * base % MOD) % MOD;
        (sum[N] *= 2) %= MOD;
        (base <<= 1) %= MOD;
    }
    printf("%lld\n", max((ssum + ssum + sum[N] * base % MOD) % MOD, (ssum + srsum + sum[N] * base % MOD) % MOD));
    // pow2s[0] = sum[N] * N % MOD, pow2[0] = 1;
    // for(int i = 1; i <= M; ++i)pow2s[i] = pow2s[i - 1] * 4 % MOD, pow2[i] = pow2[i - 1] * 2 % MOD;
    // for(int i = 0; i <= M; ++i)spow2s[i] = ((i - 1 >= 0 ? spow2s[i - 1] : 0) + pow2s[i]) % MOD;
    // ll ans((ssum * pow2[M] % MOD + spow2s[M - 1]) % MOD);
    // ssum = (ssum * pow2[M - 1] % MOD + (M - 2 >= 0 ? spow2s[M - 2] : 0)) % MOD;
    // srsum = (srsum * pow2[M - 1] % MOD + (M - 2 >= 0 ? spow2s[M - 2] : 0)) % MOD;
    // ll tmp = ssum;
    // ssum = ((ssum + srsum) % MOD + pow2s[M - 1]) % MOD;
    // ans = max(ans, ssum);
    // //unreverse todo
    // ll origin_ssum = ssum, origin_srsum = srsum;
    // for(int rev = 1; rev <= M; ++rev){
    //     ssum = origin_ssum, srsum = origin_srsum;
    //     int d = rev - 1;
    //     ssum = (ssum * pow2[d] % MOD + (rev - 2 >= 0 ? spow2s[rev - 1] : 0)) % MOD;
    //     srsum = (srsum * pow2[d] % MOD + (rev - 2 >= 0 ? spow2s[rev - 1] : 0)) % MOD;
    //     ll tmp = ssum;
    //     (ssum += (srsum + pow2s[rev]) % MOD) %= MOD;
    //     (srsum += (tmp + pow2s[rev]) % MOD) %= MOD;
    //     d = M - rev;
    //     ssum = (ssum * pow2[d] % MOD + (spow2s[M] - spow2s[rev] + MOD) % MOD) % MOD;
    //     ans = max(ans, ssum);
    //     printf("rev = %d, ssum = %lld\n", rev, ssum);
    // }
    // printf("%lld\n", ans);
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

Pjudge-21743 B. 【NOIP Round #5】青鱼和怪兽

题面

存在怪兽,你有 n 点血怪兽有 m,每次可以选择攻击,会有 p 的概率使怪兽掉一滴血,反之则有 1p 的概率使自己掉一滴血吗,会消耗 1 单位时间,也可以选择 remake,不消耗时间但会使你和怪兽恢复为初始的血量,求击杀怪兽的期望时间。

Solution

考虑一个朴素的 DP,令 dp(i,j) 表示你剩 i 怪兽剩 j 的状态时击杀的期望时间,不考虑 remake 的转移为 dp(i,j)=dp(i1,j)×(1p)+dp(i,j1)×p,然后考虑到 remake,不难想到二分答案 tim,也就是二分一个 dp(n,m),此时我们不难发现边界,对于 dp(i,0),此时已经结束,赋值为 0,对于 dp(0,j) 则只能重开,赋值为 tim,然后转移过程中在朴素转移的基础上对 timmin,即如果正常的期望不如重开,那么就直接重开。然后最终判断求得得 dp(n,m) 与我们二分出来的 tim 是否相同,相同则说明不合法,可以上调下界,反之一定有更优的低于 tim 的时间,下调上界。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#include<climits>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define EPS (1e-9)

template < typename T = int >
inline T read(void);

int N, M, C;
ld P;
ld dp[1100][1100];

bool Check(ld tim){
    for(int i = 0; i <= N + 10; ++i)
        dp[i][0] = 0.0;
    for(int j = 0; j <= M + 10; ++j)
        dp[0][j] = tim;
    for(int i = 1; i <= N; ++i)
        for(int j = 1; j <= M; ++j)
            dp[i][j] = min(tim, (dp[i - 1][j] + 1) * (1.0 - P) + (dp[i][j - 1] + 1) * P);
    return dp[N][M] < tim;
}

int main(){
    N = read(), M = read(), C = read();
    P = (ld)C / 100.0;
    ld l = EPS, r = 1e9, ans(-1.0);
    while(fabs(l - r) > EPS){
        ld mid = (l + r) / 2.0;
        ans = mid;
        if(Check(mid))r = mid - EPS;
        else l = mid + EPS;
    }printf("%.8Lf\n", ans);

    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

LG-P4097 [HEOI2013]Segment

题面

插线段,求点对应最大值且序号最小的线段。

Solution

李超线段树模板解决。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define EPS (1e-6)
#define LIM (41000)

template < typename T = int >
inline T read(void);

struct Interval{
    bool flag;
    int idx;
    double k, b;
    double CalVal(int x){
        return k * x + b;
    }
};
class LCSegTree{
private:
    Interval tr[LIM << 2];
    #define LS (p << 1)
    #define RS (LS | 1)
    #define MID ((gl + gr) >> 1)
public:
    void Update(Interval line, int p, int gl, int gr){
        if(!tr[p].flag)return tr[p] = line, void();
        if(fabs(line.CalVal(MID) - tr[p].CalVal(MID)) < EPS){
            if(line.idx < tr[p].idx)swap(tr[p], line);
        }else if(line.CalVal(MID) > tr[p].CalVal(MID))swap(tr[p], line);
        if(gl != gr && (fabs(line.CalVal(gl) - tr[p].CalVal(gl)) < EPS || line.CalVal(gl) > tr[p].CalVal(gl)))Update(line, LS, gl, MID);
        if(gl != gr && (fabs(line.CalVal(gr) - tr[p].CalVal(gr)) < EPS || line.CalVal(gr) > tr[p].CalVal(gr)))Update(line, RS, MID + 1, gr);
    }
    void UpdatePoint(int pos, int val, int idx, int p = 1, int gl = 1, int gr = LIM){
        if(gl == gr){
            if(!tr[p].flag)tr[p] = Interval{true, idx, 0.0, (double)val};
            else if(fabs(tr[p].CalVal(pos) - (double)val) < EPS)tr[p].idx = min(tr[p].idx, idx);
            else if(tr[p].CalVal(pos) < (double)val)tr[p] = Interval{true, idx, 0.0, (double)val};
            return;
        }
        if(pos <= MID)UpdatePoint(pos, val, idx, LS, gl, MID);
        else UpdatePoint(pos, val, idx, RS, MID + 1, gr);
    }
    void Insert(Interval line, int l, int r, int p = 1, int gl = 1, int gr = LIM){
        if(l <= gl && gr <= r)return Update(line, p, gl, gr);
        if(l <= MID)Insert(line, l, r, LS, gl, MID);
        if(r >= MID + 1)Insert(line, l, r, RS, MID + 1, gr);
    }
    pair < int, double > Query(int pos, int p = 1, int gl = 1, int gr = LIM){
        auto ret = tr[p].idx; auto mxv = tr[p].CalVal(pos);
        if(gl != gr){
            auto vals = pos <= MID ? Query(pos, LS, gl, MID) : Query(pos, RS, MID + 1, gr);
            if(fabs(vals.second - mxv) < EPS)ret = min(ret, vals.first);
            else if(vals.second > mxv)ret = vals.first, mxv = vals.second;
        }return {ret, mxv};
    }
}lcst;

int N;
int cnt(0);
int lst(0);

int main(){
    N = read();
    for(int i = 1; i <= N; ++i){
        int opt = read();
        if(opt == 0){
            int ans = lcst.Query((read() + lst - 1) % 39989 + 1).first;
            lst = ans;
            printf("%d\n", ans);
            continue;
        }
        int sx = (read() + lst - 1) % 39989 + 1, sy = (read() + lst - 1) % (int)(1e9) + 1;
        int tx = (read() + lst - 1) % 39989 + 1, ty = (read() + lst - 1) % (int)(1e9) + 1;
        if(sx == tx)lcst.UpdatePoint(sx = tx, max(sy, ty), ++cnt);
        else lcst.Insert(Interval{true, ++cnt, (double)(ty - sy) / (tx - sx), (double)ty - (double)(ty - sy) / (tx - sx) * tx}, min(sx, tx), max(sx, tx));
    }

    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

LG-P5249 [LnOI2019]加特林轮盘赌

题面

存在加特林,存在 n 个人围城一圈轮流使用加特林并每次均有 P 的概率被打死,求第 k 个人是最终唯一的幸存者的概率。

Solution

很有意思的一道题,和一般的朴素概率 DP 不尽相同。

前面的思路和其它题解差不多,主要细说一下最后推式子的步骤。

对于 n=1 平凡解决,对于 n=2 考虑,发现不能用一般的线性递推的思路,不难想到对于处于第一位的人,若其没有被打死,那么下一次的时候枪指向下一个人,局面与初始时原来处于第一位的人处于第二位等效,那么不难想到我们令 dp(2,i) 表示 2 个人时处于第 i 位的人幸存的概率,不难发现转移:

dp(2,1)=P×0+(1P)×dp(2,2)

也就是说考虑其原本处于第 1 位,被打死则无贡献,没有被打死并且在下一次被移动到 2 位置后仍然幸存即为答案。

写到这就不难发现这东西是存在后效性的,或者说不存在初值,但是仍然存在显然的 dp(2,1)+dp(2,2)=1,所以也可以计算。

于是考虑扩展到 dp(n,i),不难想到最朴素地:

dp(n,1)=(1P)×dp(n,n)

i=1ndp(n,i)=1

此时我们仔细想一下刚才的过程,不难发现意义就是我们每次只崩首位的人,崩完之后不移动枪,而是将整个圆排列对应旋转。

然后我们考虑对于中间的转移,不难想到对于 dp(n,k),我们如果 P 的概率崩掉首位了,则 kk1,nn1,即 P×dp(n1,k1),如果 1P 地没崩掉,那么人虽然没有减少但是圆排列仍然需要转一下 kk1,也就是 (1P)×dp(n,k1),于是转移即为:

dp(n,k)=P×dp(n1,k1)+(1P)×dp(n,k1)

当然我们这东西是没有初值的,不能直接递推,但是共存在 n 个方程,可以直接解 n1 次方程组,用高斯消元即可,但是这个是 O(n4) 的,考虑优化。

考虑对于 P×dp(n1,k1) 此时已经为常量了,令其为 ξ,则有:

dp(n,k)=(1P)×dp(n,k1)+ξ

f(k)=dp(n,k),令 ξ(k) 表示 dp(n1,k1)×P×(1P)k1,则有:

f(k)=(1P)k1f(1)+i=1k1ξ(i)

这样我们可以用 f(1) 表示出所有 f(k),然后带入 f(k)=1 求出 f(1),再 O(n) 求出所有的 f(k)

注意需要特判 P=0 的情况,且注意需要滚动数组优化空间。

最终时间复杂度 O(n2),空间复杂度 O(n)

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define EPS (1e-10)

template < typename T = int >
inline T read(void);

ld P;
int N, K;
ld dp[2][11000];

int main(){
    scanf("%Lf", &P), N = read(), K = read();
    if(P < EPS)printf("%.10Lf\n", N == 1 ? (ld)1 : (ld)0), exit(0);
    dp[1][1] = 1.0;
    bool cur(false);
    for(int i = 2; i <= N; ++i){
        ld K(1.0), B(0.0), base1(1.0), base2(0.0);
        for(int j = 2; j <= i; ++j)
            base1 *= (1 - P), base2 = base2 * (1 - P) + P * dp[cur ^ 1][j - 1],
            K += base1, B += base2;
        dp[cur][1] = (1 - B) / K;
        for(int j = 2; j <= i; ++j)
            dp[cur][j] = (1 - P) * dp[cur][j - 1] + P * dp[cur ^ 1][j - 1];
        cur ^= 1;
    }printf("%.10Lf\n", dp[cur ^ 1][K]);
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

UPD

update-2023_02_25 初稿

本文作者:tsawke

本文链接:https://www.cnblogs.com/tsawke/p/17180247.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Tsawke  阅读(34)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起