省选测试14

省选测试14

T1

你有一个字符串 (S_0)​,想要用它造出共 n 个新字符串 (S_{0},cdots,S_{n-1}​)

你会按顺序进行 n-1 次操作,造出新字符串。第 i 次操作有两种方法:

S x l r 截取 (S_x)​ 的 [l,r) 子串,即第 l 到第 r-1 个字符作为 (S_i)

A x y 将 (S_x)(S_y) 拼接作为 (S_i)


最后,你会关心 (S_{n-1})​ 长什么样,因此请输出 (S_{n-1})​ 所有字符的 ASCII 码之和,结果对 (10^9+7)取模。

本题中字符串下标均从 0 开始编号,且保证所有字符串串长不超过 (2^{63}-1),并且所有操作均合法(即(0leq x,y<i,0leq l < rleq Len(S_x))

对于全部数据,(nleq 2000, 1leq Len(S_0)leq 2000),同时满足题目描述中给的限制。

搜索 + 记忆化.

md传参类型传错了少了40pts...

直接分情况搜索就好了, 记录一个第一个串的前缀和.

不过我写的记搜比较慢, 我只记录了一个第(i)个字符串的整个的价值是多少, 但是题解记录的是第(i)个字符串前(l)个字符的价值是多少, 可能用的空间大了点(但这题不卡空间).

#include <bits/stdc++.h>

#define ull unsigned long long

using namespace std;

inline ull read() {
    ull s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 2005, mod = 1e9 + 7;
int n;
int sum[N], ans[N];
ull len[N];
char ch[N];
struct ques { int opt, x; ull l, r; } q[N];

int calc_qj(int x, ull l, ull r) {
    if(x == 0) return (sum[r] - sum[l - 1] + mod) % mod;
    if(r - l + 1 == len[x]) return ans[x];
    if(q[x].opt == 2) {
        if(r <= len[q[x].l]) return calc_qj(q[x].l, l, r);
        else if(l > len[q[x].l]) return calc_qj(q[x].r, l - len[q[x].l], r - len[q[x].l]);
        else return (calc_qj(q[x].l, l, len[q[x].l]) + calc_qj(q[x].r, 1, r - len[q[x].l])) % mod;
    }
    if(q[x].opt == 1) {
        return calc_qj(q[x].x, l + q[x].l - 1, r + q[x].l - 1);
    }
}

int calc(int x) {
    if(ans[x]) return ans[x];
    // cout << x << " " << q[x].l << " " << q[x].r << "
";
    if(q[x].opt == 2) {
        ans[x] = (calc(q[x].l) + calc(q[x].r)) % mod;
        len[x] = len[q[x].l] + len[q[x].r];
    }
    else {
        ans[x] = calc_qj(q[x].x, q[x].l, q[x].r);
        len[x] = q[x].r - q[x].l + 1;
        // cout << x << ":" << q[x].r << " " << q[x].l << "
";
    }
    return ans[x];
}

int main() {

    // freopen("string.in","r",stdin); freopen("string.out","w",stdout);

    scanf("%d", &n);
    scanf("%s", ch + 1);
    int len_ch = strlen(ch + 1);
    for(int i = 1;i <= len_ch; i++) sum[i] = (sum[i - 1] + ch[i]) % mod;
    ans[0] = sum[len_ch]; len[0] = len_ch;
    // for(int i = 1;i <= len_ch; i++) cout << i << ":" << sum[i] << "
";
    for(int i = 1;i < n; i++) {
        char ch_; cin >> ch_;
        if(ch_ == 'S') {
            q[i].opt = 1; q[i].x = read(); q[i].l = read() + 1; q[i].r = read();
        }
        if(ch_ == 'A') {
            q[i].opt = 2; q[i].l = read(); q[i].r = read();
        }
    }
    for(int i = 1;i < n - 1; i++) {
        calc(i);
        // cout << i << ":" << ans[i] << " " << len[i] << "
";
    }
    printf("%d
", calc(n - 1));

    fclose(stdin); fclose(stdout);

    return 0;
}

T2

你有 n 株植物,高度为 (a_i)​。你希望让它们的高度看起来不太相同,具体来说:任意两株高度差的绝对值不小于 d。

每次你可以选择一株植物,将其拔高或压低 1,这花费你 1 的力气,这个操作可以进行任意多次。注意,植物高度不能是负的,即你需要保证任意时刻 (a_igeq 0)

你最少需要使用多少力气,才能使得植物高度看起来不太相同呢?

多组测试数据。第一行一个整数 T 为数据组数,接下来每组格式如下:

第一行两个整数 n,d。

接下来一行 n 个整数 (a_i)​。

对于全部数据,(sum nleq 3cdot 10^5,1leq dleq 10^6, 0leq a_ileq 10^{11})

开始想了了费用流的做法, 骗了60pts.

就是先给每个(a_i)排序, 然后求出所有 (x = a_{i+1} - a_i - d), 对于(x)小于0的就从源点向他连一条流量为(abs(x)), 费用为0的边, 对于大于0的就从他向汇点连一条流量为(x), 费用为0的边. 然后这些点之间都连双向的流量为(inf), 费用为1的边. 为啥呢?

如果说提高一个(i)的高度的话, (i-1)(i)的高度差就会增加1, (i+1)(i)的高度差就会减小1, 然后又花了1的体力. 都减(d)是因为要保证相邻两个的差值大于等于d.

但是还有特殊的两个节点, 就是最小的那个可以压到0, 最大的那个可以无限高, 于是再连两条特殊的边就好了 : 从0向汇点连一条流量为(a_1), 费用为0的边, 从(n+1)向汇点连一条流量为(inf), 费用为0的边.

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
    long long s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 4e5, M = 3e6;
const long long inf = 1e18, Inf = 1e9;
int s, t, n, d_, cnt;
int d[N], pre[N], las[N], head[N];
long long b[N], a[N], incf[N];
struct edge { int v, to, nxt; long long c; } e[M];

void add(int x, int y, long long z1, int z2) {
    // cout << x << " " << y << " " << z1 << " " << z2 << "!!!!
";
    e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y; e[cnt].c = z1; e[cnt].v = z2;
    e[++ cnt].nxt = head[y]; head[y] = cnt; e[cnt].to = x; e[cnt].c = 0; e[cnt].v = -z2;
}

int bfs() {
    for(int i = s;i <= t; i++) d[i] = Inf, incf[i] = inf;
    queue <int> q; d[s] = 0; pre[t] = -1; q.push(s);
    while(q.size()) {
        int x = q.front(); q.pop();
        for(int i = head[x]; i ; i = e[i].nxt) {
            int y = e[i].to;
            if(e[i].c && d[y] > d[x] + e[i].v) {
                d[y] = d[x] + e[i].v; q.push(y);
                incf[y] = min(incf[x], e[i].c);
                pre[y] = x; las[y] = i;
            }
        }
    }
    return pre[t] != -1;
}

void Work() {
    long long res = 0;
    while(bfs()) {
        int now = t;
        res += 1ll * d[t] * incf[t];
        // cout << d[t] << " " << incf[t] << "!!!!!
";
        while(now != s) {
            e[las[now]].c -= incf[t];
            e[las[now] ^ 1].c += incf[t];
            now = pre[now];
        }
    }
    printf("%lld
", res);
}

void Clear() {
    cnt = 1; s = 0; t = n + 2;
    memset(head, 0, sizeof(head));
}

void Init() {
    n = read(); d_ = read();
    Clear();
    for(int i = 1;i <= n; i++) a[i] = read();
    sort(a + 1, a + n + 1);
    for(int i = 2;i <= n; i++) b[i] = a[i] - a[i - 1] - d_; 
    add(1, t, a[1], 0); 
    for(int i = 2;i <= n; i++) 
        if(b[i] < 0) add(s, i, -b[i], 0);
        else add(i, t, b[i], 0);
    add(n + 1, t, inf, 0);
    for(int i = 1;i <= n + 1; i++) {
        if(i != 1) add(i, i - 1, inf, 1);
        if(i != n + 1) add(i, i + 1, inf, 1);
    }
}

int main() {

    freopen("different.in","r",stdin); freopen("different.out","w",stdout);

    for(int T = read(); T ; T --) {
        Init(); Work();
    }

    fclose(stdin); fclose(stdout);

    return 0;
}

/*
2
4 10
1 100 7 10
5 1
1 1 1 1 1

1
5 1
1 1 1 1 1
*/

接下来说说正解吧.

正解是DP.其实我知道该用DP写这道题, 但是我不会qwq.

首先有一个经典的转化 : (A_i = a_i - (i-1)*d). 那么问题就转化成了将(A_i)序列变成单调不降序列的最小代价.(注意(a_i)是排过序的)

(f_{i,j})表示前(i)个元素转化成单调不降序列, 并且(A_i = j)的最小代价.那么可以列出转移方程 :

[ f_{i,j} = |A_i - j| +min{ f_{i-1, -infty - j} } ]

怎么优化呢? 我们可以考虑一下它的函数图像是什么, 我们如果可以找到它的最低点就好了.

先画一下(f_{1,j} = |A_1 - j|)的图像 : 就是普通的绝对值函数图像, 没有啥好画的.

然后在考虑一下(f_{2,j} = |A_2 - j| + min{ f_{1, -infty-j} })的图像是什么, 就是一段绝对值函数和刚刚(f_1)的前缀最小值函数合并一下, 类似一个下凸壳.

最后(f_{n,j})的图像也是一个下凸壳, 并且斜率是连续的, 0, -1, -2, (dots), -k.

最后的答案就是(min{ f_{n,j} }), 我们需要找到函数图像最低的点在哪里.

(s_i)是下凸壳上定点的横坐标, 并且从大到小排序.

那么最后的答案就是(f_{n,0} - sum_{i=1}^{n-1} i * (s_i-s_{i-1})), (i)就是那个斜率嘛, 是连续的.

现在就考虑(f_{n_0})怎么求就好了. 因为要求(f_{n,0}), 所以(A_n = 0), 又因为(A_1 = a_1 geq 0), 所以整个(A_i)序列一定都是0, 因为要保证单调不降, 那么很自然地, (f_{n,0} = sum_{i=1}^{n} A_i).

具体的维护这个图像的拐点要用到堆, 看下代码就好了.

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
    long long s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 3e5 + 5;
long long a[N], s[N];
priority_queue <long long> q;

int main() {

    for(int T = read(); T ; T --) {
        int n = read(), d = read(); 
        for(int i = 1;i <= n; i++) a[i] = read();
        sort(a + 1, a + n + 1);
        for(int i = 1;i <= n; i++) {
            long long x = max(0ll, a[i] - 1ll * (i - 1) * d);
            q.push(x); q.push(x); q.pop();
        }
        int cnt = 0;
        long long res = 0;
        for(int i = 1;i <= n; i++) res += abs(a[i] - 1ll * (i - 1) * d);
        while(q.size()) s[++ cnt] = q.top(), q.pop(); s[++ cnt] = 0;
        for(int i = 1;i < cnt; i++) res -= (s[i] - s[i + 1]) * i;
        printf("%lld
", res);
    }

    return 0;
}

T3

你有 2n+m 根绳子,每根绳子两端染有黑、白两种颜色。

其中 n 根绳子两端都是黑色; n 根绳子两端都是白色; m 根绳子一段黑色另一端白色。

现在绳子一端为黑色和白色的线头分别有 2n+m 个,你想要把黑色和白色一端的线头随机配对(共 (2n+m)! 种方法),每对配对的线头用胶水拼接起来。可以发现,这样所有绳子一定会绕成若干个环。

你想要求出环的个数期望是多少,结果对 (10^9+7) 取模。

一行两个整数 n,m。

对于全部数据,(0leq n,mleq 10^6)

一看期望就头疼啊md.

直接设(f_{n,m})是两端都为0的线段有(n)条, 两端都为1的线段有(m)条, 一端为0一端为1的有(m)条的期望答案是多少. 最后输出(f_{n,m})就好了.

然后分情况讨论, 我们拿出一条一端是0, 一端是1的来和别的组合 :

1.和两端都是0或两端都是1的连接, 那么连完之后会得到两端都是1或两端都是0的(注意顺序), 并没有形成环, 这样的一共有(2n)种方案.

2.和一端为1一端为0的连(不包括自己), 连完之后会得到一端为0一端为1的, 并没有形成环, 这样的一共有(m-1)种方案.

3.自己首尾相连, 连完之后会得到一个环, 这样的一共有(1)种方案.

那么总共有(2n+m)中方案, 有贡献的只有1个, 那么可以得到转移方程 :

[f_{n,m} = f_{n,m-1} + frac{1}{2n+m} ]

如果说我们知道了(f_{n,0})的话就可以(O(n))求答案了.

两端为0的只能和两端为1的去连, 因为没有一端是0一端是1的了, 再结合上面那个式子就可以得到 :

[f_{n,0} = f_{n-1,1} = f_{n-1,0} + frac{1}{2n-1} ]

也是可以(O(n))求的.

代码非常短 :

#include <bits/stdc++.h>

using namespace std;

inline long long read() {
    long long s = 0, f = 1; char ch;
    while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
    for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
    return s * f;
}

const int N = 1e6 + 5, mod = 1e9 + 7;
int n, m, ans;

int ksm(int x, int y) {
    int res = 1;
    while(y) { if(y & 1) res = 1ll * res * x % mod; x = 1ll * x * x % mod; y >>= 1; }
    return res;
}

int main() {

    n = read(); m = read();
    for(int i = 0;i < n; i++) ans = (ans + ksm(2 * i + 1, mod - 2)) % mod;
    for(int i = 1;i <= m; i++) ans = (ans + ksm(2 * n + i, mod - 2)) % mod;
    printf("%d", ans);

    return 0;
}
原文地址:https://www.cnblogs.com/czhui666/p/14539868.html