省选测试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\),并且所有操作均合法(即\(0\leq x,y<i,0\leq l < r\leq Len(S_x)\)
对于全部数据,\(n\leq 2000, 1\leq 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 << "\n";
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 << "\n";
}
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] << "\n";
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] << "\n";
}
printf("%d\n", calc(n - 1));
fclose(stdin); fclose(stdout);
return 0;
}
T2
你有 n 株植物,高度为 \(a_i\)。你希望让它们的高度看起来不太相同,具体来说:任意两株高度差的绝对值不小于 d。
每次你可以选择一株植物,将其拔高或压低 1,这花费你 1 的力气,这个操作可以进行任意多次。注意,植物高度不能是负的,即你需要保证任意时刻 \(a_i\geq 0\)。
你最少需要使用多少力气,才能使得植物高度看起来不太相同呢?
多组测试数据。第一行一个整数 T 为数据组数,接下来每组格式如下:
第一行两个整数 n,d。
接下来一行 n 个整数 \(a_i\)。
对于全部数据,\(\sum n\leq 3\cdot 10^5,1\leq d\leq 10^6, 0\leq a_i\leq 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 << "!!!!\n";
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] << "!!!!!\n";
while(now != s) {
e[las[now]].c -= incf[t];
e[las[now] ^ 1].c += incf[t];
now = pre[now];
}
}
printf("%lld\n", 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_{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\n", res);
}
return 0;
}
T3
你有 2n+m 根绳子,每根绳子两端染有黑、白两种颜色。
其中 n 根绳子两端都是黑色; n 根绳子两端都是白色; m 根绳子一段黑色另一端白色。
现在绳子一端为黑色和白色的线头分别有 2n+m 个,你想要把黑色和白色一端的线头随机配对(共 (2n+m)! 种方法),每对配对的线头用胶水拼接起来。可以发现,这样所有绳子一定会绕成若干个环。
你想要求出环的个数期望是多少,结果对 \(10^9+7\) 取模。
一行两个整数 n,m。
对于全部数据,\(0\leq n,m\leq 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,0}\)的话就可以\(O(n)\)求答案了.
两端为0的只能和两端为1的去连, 因为没有一端是0一端是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;
}