2024 信友队 noip 冲刺 8.29

T1

模拟即可。

const int maxn = 2e5 + 5;
pair<int, int> que[maxn << 1];
int head = 1, tail = 0;
int main() {
    int Q; scanf("%d", &Q);
    while (Q --) {
        int op; scanf("%d", &op);
        if (op == 1) {
            int x, c; scanf("%d %d", &x, &c);
            que[++ tail] = { x, c };
        } else {
            int x; scanf("%d", &x);
            long long sum = 0;
            while (x > 0) {
                auto [num, tot] = que[head];
                if (x >= tot) { sum += 1ll * num * tot, x -= tot; head ++; }
                else sum += 1ll * x * num, que[head] = { num, tot - x }, x = 0;
            } printf("%lld\n", sum);
        }
    }
    return 0;
}

T2

考虑对第 i i i 个点计算 L ∈ [ 1 , i ] , R = i L\in[1,i],R=i L[1,i],R=i 的答案,找到 x , y x,y x,y 中出现最晚的比较靠前的位置 k k k(找不到则暂时无解), L ∈ [ 1 , k ] L\in[1,k] L[1,k] 都是合法的。若出现 > x >x >x < y <y <y 的数则之后的答案将这个数的后一位视为 1 1 1​。

const int maxn = 2e5 + 5;
int a[maxn], x, y, n;
int mx[maxn], mn[maxn];
int main() {
    scanf("%d %d %d", &n, &x, &y); mx[0] = mn[0] = -1;
    for (int i = 1; i <= n; i ++) {
        scanf("%d", &a[i]);
        if (a[i] > x || a[i] < y) { mx[i] = mn[i] = -1; continue; }
        mx[i] = (a[i] == x ? i : mx[i - 1]), mn[i] = (a[i] == y ? i : mn[i - 1]);
    } long long ans = 0;
    for (int i = 1, lst = 0; i <= n; i ++) {
        if (a[i] > x || a[i] < y) lst = i;
        if (mn[i] == -1 || mx[i] == -1) continue;
        ans += min(mn[i], mx[i]) - lst;
    } printf("%lld", ans);
    return 0;
}

T3

如果有数量相等的球直接换即可。否则需要构造。设初始 x , y x,y x,y 数量的球被消去,令 x ≤ y x\le y xy,剩下一种颜色数量为 z z z;那么考虑将 x , z x,z x,z 不断换成 y y y 使得 x = y x=y x=y 后进行消除。设要换 k k k 次,那么有
x − k = y + 2 k ∴ k = x − y 3 x-k=y+2k\\ \therefore k=\cfrac{x-y}{3} xk=y+2kk=3xy
x − y ≡ 0 ( m o d 3 ) x-y\equiv0\pmod3 xy0(mod3) 成立,那么最终就需要 x x x 次,否则此时无解。如果一开始 z z z 不够换的,那么可以先将 x , y x,y x,y 换成 z z z 再开始上述步骤,程序里没有表现出来。一开始以为 z z z 不够大就无解挂了三发。

#define int long long
const int inf = 1e18;
int check(int x, int y, int z) {
    if (z > x) return inf;
    if (x % 3 != z % 3) return inf;
    return x;
}
signed main() {
    int T, r, g, b;
    for (scanf("%lld", &T); T --; ) {
        int ans = inf;
        scanf("%lld %lld %lld", &r, &g, &b); 
        if (r == g || r == b || g == b) {
            if (r == g || r == b) ans = min(ans, r);
            if (g == b) ans = min(ans, g);
        } 
        int S[6] = { check(r, g, b), check(r, b, g), check(b, r, g), check(b, g, r), check(g, r, b), check(g, b, r) };
        for (int i = 0; i < 6; i ++) ans = min(ans, S[i]);
        if (ans == inf) ans = -1;
        printf("%lld\n", ans);
    }
    return 0;
}

T4

一开始想了个以满足前 i i i 个数字为状态的 dp,发现会重,糖丸了。

考虑一个等价转换:对于数字 i i i,我们将它所在的两张(或者一张,此时为自环)卡连一条边。对于建出的图,边代表数字,点代表卡牌, 1 , 2 , ⋯   , n 1,2,\cdots,n 1,2,,n 至少出现一次转化为每个端点至少被一条边覆盖。由于每个点连出去两条边(正反各一个数字),最终的图一定由若干个不相交的环组成,环与环之间独立,只需计算每个环的答案然后乘起来即可。注意到环的答案只与环的大小有关,设大小为 m m m 的环答案为 f ( m ) f(m) f(m),手模一下:
f ( 1 ) = 1 , f ( 2 ) = 3 , f ( 3 ) = 4 , f ( 4 ) = 7 , ⋯ ⇒ f ( m ) = f ( m − 1 ) + f ( m − 2 ) f(1)=1,f(2)=3,f(3)=4,f(4)=7,\cdots \Rightarrow f(m)=f(m-1)+f(m-2) f(1)=1,f(2)=3,f(3)=4,f(4)=7,f(m)=f(m1)+f(m2)
环的大小用并查集维护即可。

const int maxn = 2e5 + 5, P = 998244353;
int n, p[maxn];
namespace DSU {
    int fa[maxn], siz[maxn];
    int find(int u) { return fa[u] == u ? u : fa[u] = find(fa[u]); }
    void Union(int u, int v) {
        u = find(u), v = find(v);
        if (u == v) return ;
        siz[u] += siz[v], fa[v] = u;
    }
    void init1() { for (int i = 1; i <= n; i ++) fa[i] = i, siz[i] = 1; }
} using namespace DSU;
namespace Fab {
    int f[maxn];
    void init2() {
        f[1] = 1, f[2] = 3;
        for (int i = 3; i <= n; i ++) f[i] = (1ll * f[i - 1] + 1ll * f[i - 2]) % P;
    }
} using namespace Fab;
int main() {
    scanf("%d", &n); init1(), init2();
    for (int i = 1, x; i <= n; i ++)
        scanf("%d", &x), p[x] = i;
    for (int i = 1, x; i <= n; i ++)
        scanf("%d", &x), Union(p[x], i);
    int ans = 1;
    for (int i = 1; i <= n; i ++) 
        if (fa[i] == i) ans = (1ll * ans * f[siz[i]]) % P;
    return printf("%d", ans), 0;
}

T5

考虑贪心,先不考虑 m m m 的限制,我们找到一段后缀,使得后缀和除以后缀长度最大,然后把 S S S 均匀分配到这段后缀的每个位置上即可。 m m m 的限制可能会使找到一段后缀后 S S S 分配不完,那么我们把这段后缀抠掉接着找即可。复杂度 O ( n 2 ) O(n^2) O(n2)​。

一开始找后缀时找的是 [ max ⁡ { a i } , l s t ] [\max\{a_i\},lst] [max{ai},lst],没看数据范围导致不敢想 O ( n 2 ) O(n^2) O(n2) 的暴力找。

const int maxn = 5005;
int n, m, a[maxn]; double s;
#define ll long long
ll sum[maxn]; double x[maxn];
int main() {
    scanf("%d %d %lf", &n, &m, &s);
    for (int i = 1; i <= n; i ++)
        scanf("%d", &a[i]);
    for (int i = n; i >= 1; i --) sum[i] = sum[i + 1] + a[i];
    for (int lst = n; s > 0 && lst > 0; ) {
        int pos; double mx = -1;
        for (int i = 1; i <= lst; i ++)
            if (1.0 * (sum[i] - sum[lst + 1]) / (lst - i + 1) > mx)
                pos = i, mx = 1.0 * (sum[i] - sum[lst + 1]) / (lst - i + 1);
        double ss = s;
        for (int len = lst - pos + 1; lst >= pos; lst --)
            x[lst] = min(1.0 * s / len, 1.0 * m), ss -= x[lst];
        s = ss;
    } double ans = 0;
    for (int i = 1; i <= n; i ++) 
        ans += x[i] * a[i];
    return printf("%.8lf", ans), 0;
} 

T6

赛时并没想到这个非常套路的 dp 状态设计。

先考虑一下对于一段序列 [ l , r ] [l,r] [l,r] 能否将 [ l + 1 , r − 1 ] [l+1,r-1] [l+1,r1] 全部删除如何判断。显然如果有相邻的两个数相等就不行,其次如果 [ l , r ] [l,r] [l,r] 中只有两种值那也不行(2 1 2 等情况除外,特判即可),因为删掉一个元素后立即会出现两个相邻且相等的数。考虑不满足这些条件的序列是否可以被删成只剩左右端点。比如说 a = { 1 , 2 , 3 , 2 , 1 } a=\{1,2,3,2,1\} a={1,2,3,2,1},我们发现 3 3 3 若被删去那么会出现相邻的两个 2 2 2,考虑以这个 3 3 3 为端点删掉两个 2 2 2 之后再把 3 3 3 删了即可。于是我们得到了 [ l , r ] [l,r] [l,r] 能把 [ l + 1 , r + 1 ] [l+1,r+1] [l+1,r+1] 删掉的条件:

  • [ l , r ] [l,r] [l,r] 中值的种类数 ≥ 3 \ge3 3
  • [ l , r ] [l,r] [l,r] 不存在相邻两个相等的数。

对于计算方案数,我们考虑 dp,设 f ( i ) f(i) f(i) 表示只考虑 [ 1 , i ] [1,i] [1,i] i i i 为右端点的答案。显然可以啥都不删,即 f ( i ) ← f ( i ) + f ( i − 1 ) f(i)\gets f(i)+f(i-1) f(i)f(i)+f(i1)。考虑找到一段后缀 [ j , i ] [j,i] [j,i] 满足 [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i1] 能被删完,那么答案就可以是 f ( j ) f(j) f(j) 的方案数和考虑 [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i1] 删不删,不删的情况在 f ( i − 1 ) f(i-1) f(i1) 中,所以直接 f ( i ) ← f ( i ) + f ( j ) f(i)\gets f(i)+f(j) f(i)f(i)+f(j)

若直接枚举 j j j 判断 [ i , j ] [i,j] [i,j] 中间是否可删是 O ( n 2 ) O(n^2) O(n2) 的。在扫的过程中,如果发现了一对 a i = a i − 1 a_i=a_{i-1} ai=ai1,那么 f ( i − 1 ) f(i-1) f(i1) 及以前的答案都取不到了;值的种类数随着区间长度的缩小而减少。于是我们考虑双指针,维护两个指针 l , r l,r l,r,对于 j ∈ [ l , r ) j\in[l,r) j[l,r) 表示 [ i , j ] [i,j] [i,j] [ i + 1 , j − 1 ] [i+1,j-1] [i+1,j1] 是可删除的。每次扫到 a i a_i ai 时考虑移动 l l l,然后考虑移动 r r r,用一个桶记录当前 [ r , i ] [r,i] [r,i] 值的种类数, r r r 一直右移直到不合法或 r = i − 2 r=i-2 r=i2(此时再短就不可能删了),如果 l < r l<r l<r 那么 ∀ j ∈ [ l , r ) \forall j\in[l,r) j[l,r) f ( j ) f(j) f(j) 的答案都能取,维护一个前缀和即可。时间复杂度 O ( n ) O(n) O(n)

const int maxn = 2e5 + 5;
int a[maxn], f[maxn], sum[maxn], n;
int table[maxn], tot;
void add(int val) { tot += ((++ table[val]) == 1); }
void del(int val) { tot -= ((-- table[val]) == 0); }
const int P = 998244353;
int main() {
    scanf("%d", &n), f[0] = 1;
    for (int i = 1, l = 1, r = 1; i <= n; i ++) {
        scanf("%d", &a[i]); f[i] = f[i - 1];
        if (a[i] == a[i - 1]) l = i;
        if (i > 2 && a[i] != a[i - 1] && a[i - 1] != a[i - 2]) f[i] = (1ll * f[i] + 1ll * f[i - 2]) % P;
        for (add(a[i]); r < i - 2 && tot >= 3; del(a[r ++]));
        if (l <= r) f[i] = (1ll * f[i] + 1ll * (1ll * sum[r - 1] - 1ll * sum[l - 1] + P) % P) % P;
        sum[i] = (1ll * sum[i - 1] + 1ll * f[i]) % P;
    } printf("%d", f[n]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值