省选测试16
好久没有考场上 A题了
A 环
题目大意 : 构造出一个 01 序列使得存在一个 1 满足这个 1 后面是 0,且这个 1 和后面的 0 交换后与原序列循环同构
- 你要移动一个数让他还是循环同构
- 比如说样例
101011010101
移动了第4个1他就循环同构了, 因为每次都移动的是1,所以按1分开
101011010101
2 2 12 2 2 1
- 移动一下
101010110101
2 2 2 12 2 1
- 发现也就是让下面那个2212221循环同构,把这个换成01串
1101110
12 112
- 移动
1110110
112 12
- 发现还是让下面那个循环同构
- 设一共n个数,让k个数为1,然后就发现下面那个序列就是一共k个数,让n%k个数为1,递归找就行
- 找到了一个构造就好说了,我直接就枚举了整体移动多少位,然后暴力判断
Code
Show Code
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 105;
int a[N], b[N], p[N];
void Solve(int l, int k) {
if (!k) return a[1] = -1, void();
if (k == 1) return a[1] = 1, void();
Solve(k, l % k);
if (a[1] == -1) return;
memcpy(b + 1, a + 1, k * 4);
int cnt = 0, tmp = l / k;
for (int i = 1; i <= k; ++i) {
a[++cnt] = 1;
for (int j = b[i] + tmp - 1; j; --j)
a[++cnt] = 0;
}
}
bool Judge(int l) {
int cnt = 0;
for (int i = 1; i <= l && cnt <= 2; ++i)
if (a[i] != b[i]) p[++cnt] = i;
int x = p[1], y = p[2];
if (cnt != 2) return 0;
if (x + 1 == y && b[x] && !b[y] && !a[x] && a[y]) return 1;
if (x == 1 && y == l && !b[x] && b[y] && a[x] && !a[y]) return 1;
return 0;
}
void Next(int l) {
memcpy(b + 1, a + 1, l * 4);
for (int d = 0; d < l; ++d) {
for (int i = 1; i <= l; ++i)
a[(i+d-1)%l+1] = b[i];
if (Judge(l)) return;
}
}
int main() {
int T; scanf("%d", &T);
while (T--) {
int l, k, n;
scanf("%d%d%d", &l, &k, &n);
memset(a, 0, sizeof(a));
Solve(l, k);
if (a[1] == -1) puts("NO");
else {
puts("YES");
while (n--) {
for (int i = 1; i <= l; ++i)
printf("%d", a[i]);
puts("");
Next(l);
}
}
}
return 0;
}
B DNA序列
题目大意 : 给定 n 个字符串,每个串选一个前缀,以任意顺序拼起来,令得到的串字典序最小
-
考虑依次区的部分分,很容易想到从前往后贪心的选择前缀,发现这样是不对的,正确的应该是从后往前贪。复杂度n的4次方
-
然后就是看什么样的顺序可以贪心成功
-
题解上就是找到一个最短的前缀x,使得x重复无限次后字典序比原串小
-
然后找到一个后缀y,满足x不是y的前缀
-
排序把x当作第一关键字从小到大排序,把y当作第二关键字从大到小排序,然后贪心就好了
Code
Show Code
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 55;
int n;
string ans;
struct Node {
string s, x, y;
}a[N];
bool operator < (const Node &a, const Node &b) {
return a.x != b.x ? a.x < b.x : a.y > b.y;
}
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i].s; a[i].s += 'Z';
int len = a[i].s.size(), x, y;
for (x = 1; x <= len; ++x) {
string tmp = a[i].s.substr(0, x), s;
for (int j = 1; j <= 50; ++j) s += tmp;
if (s < a[i].s) break;
}
a[i].x = a[i].s.substr(0, x); y = x;
while (a[i].s.substr(y, x) == a[i].x) y += x;
a[i].y = a[i].s.substr(y);
for (int j = 1; j <= 6; ++j)
a[i].x += a[i].x;
}
sort(a + 1, a + n + 1);
for (int i = n; i >= 1; --i) {
int len = a[i].s.size();
string lt = ans;
ans = a[i].s[0] + ans;
for (int j = 2; j <= len; ++j)
ans = min(ans, a[i].s.substr(0, j) + lt);
}
cout << ans;
}
C 探寻
题目大意 : 给定一颗树,第一次经过一条边有花费,第一次到达一个点会有收益,问从根到终点最小透支多少
-
首先把终点的收益设为inf,这样就可以去贪心的找了
-
对每个节点按费用从小到大排序,如果收益是负数的话就放在后面
-
考虑取出一个点 x,找到 x 的祖先中第一个没有被选过的点 fx
-
如果 fx 不存在,说明 x 可以被直接选
-
如果有 fx,就把 x 和 fx 合并到一个点
-
如果 w[fx] > c[x], 说明走父节点所拿的钱足够走到 x,就让 fx 的收益加上 w[x] - c[x]
-
否则走父节点的代价还会增多,就让 fx 的代价加上 c[x] - w[fx],fx 的收益就变成了 w[x]
Code
Show Code
#include <set>
#include <cstdio>
using namespace std;
const int N = 2e5 + 5;
int read(int x = 0, int f = 1, char c = getchar()) {
for (; c < '0' || c > '9'; c = getchar())
if (c == '-') f = -1;
for (; c >='0' && c <='9'; c = getchar())
x = x * 10 + c - '0';
return x * f;
}
bool v[N];
int n, fa[N], f[N];
long long w[N], c[N], ans, sum;
int Find(int x) {
return x == f[x] ? x : f[x] = Find(f[x]);
}
bool Cmp(int x, int y) {
return (w[x] > c[x]) != (w[y] > c[y]) ? w[x] > c[x] : (c[x] != c[y] ? c[x] < c[y] : x < y);
}
set<int, bool(*)(int, int)> s(Cmp);
int main() {
n = read(); v[0] = 1;
for (int i = 1; i < n; ++i) {
fa[i] = read(), w[i] = read(), c[i] = read();
if (w[i] == -1) w[i] = 1e15;
s.insert(i); f[i] = i;
}
while (!s.empty()) {
int x = *s.begin(); s.erase(s.begin());
v[x] = 1; f[x] = fa[x];
int fx = Find(x);
if (v[fx]) ans = min(ans, sum -= c[x]), sum += w[x];
else {
s.erase(s.find(fx));
if (w[fx] > c[x]) w[fx] += w[x] - c[x];
else c[fx] += c[x] - w[fx], w[fx] = w[x];
s.insert(fx);
}
}
printf("%lld\n", -ans);
return 0;
}