英语巨烂的我,把两个签到题读成了不可写题…感觉给我一个中文题面,有机会ak…
A. Rooms and Passages
题意:有 n + 1个点在一排,有 n 条边连接,依次求点 i 往 点 n 的方向走最多能走多远,如果当前的边权值为 x (x > 0),且已经走过一条权值为 -x 的边,那么这条边不能走。
思路:倒着处理,标记每条正边在当前出现的最小坐标即可。
#include
#define ll long long
using namespace std;
const int maxn = 5e5 + 10;
int vis[maxn], a[maxn];
stack<int> s;
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int pre = 0;
for (int i = n; i; i--) {
if (a[i] > 0) {
s.push(++pre);
vis[a[i]] = i;
}
else {
if (!vis[-a[i]])
pre = min(pre + 1, n - i + 1), s.push(pre);
else {
pre = min(pre + 1, vis[-a[i]] - i);
s.push(pre);
}
}
}
while (!s.empty())
printf("%d ", s.top()), s.pop();
}
B. Rearrange Columns
题意:要求交换一些列,使得 # 互相连通,签到题
#include
#define ll long long
using namespace std;
const int maxn = 1010;
char s[2][maxn], str[2][maxn];
int main() {
cin>>s[0]>>s[1];
int n = strlen(s[0]);
int t1 = 0, t2 = 0, t = 0;
for (int i = 0; i < n; i++) {
int f1 = 0, f2 = 0;
if (s[0][i] == '#')
f1 = 1;
if (s[1][i] == '#')
f2 = 1;
if (f1 && f2)
t++;
else if (f1)
t1++;
else if (f2)
t2++;
}
if (t1 && t2 && !t)
return puts("NO"), 0;
for (int i = 0; i < t1; i++)
str[0][i] = '#', str[1][i] = '.';
for (int i = 0; i < t; i++)
str[0][i + t1] = str[1][i + t1] = '#';
for (int i = 0; i < t2; i++)
str[0][i + t1 + t] = '.', str[1][i + t1 + t] = '#';
for (int i = t1 + t2 + t; i < n; i++)
str[0][i] = str[1][i] = '.';
if (strcmp(str[0], s[0]) == 0) {
for (int i = 0; i < t2; i++)
str[0][i] = '.', str[1][i] = '#';
for (int i = 0; i < t; i++)
str[0][i + t2] = str[1][i + t2] = '#';
for (int i = 0; i < t1; i++)
str[0][i + t2 + t] = '#', str[1][i + t2 + t] = '.';
}
puts("YES");
cout<<str[0]<<endl;
cout<<str[1]<<endl;
}
C. Jumps on a Circle
题意:有一个长度为 p 的环,你要走n次,第 i 次走 i 步,求能走多少个不同的点
思路:n很大,其实有用的n就是2 * p,因为走2 * p步肯定回到原点(等差数列求和是 p 的倍数),第 2 * p + 1其实就是第 1 步,所以我们对 n 取一个min(n , 2 * p)然后模拟走一遍就可以了
#include
#define ll long long
using namespace std;
const int maxn = 1e7 + 5;
bitset<maxn> a;
int main() {
int p;
ll n;
cin>>p>>n;
n = min(n, 2ll * p);
int k = 0;
for (int i = 0; i <= n; i++) {
k += i;
k %= p;
a[k] = 1;
}
printf("%d\n", a.count());
}
D. Country Division
题意:给一棵树,q次询问,每次询问把树中一些点染红色,一些点染蓝色,求是否能够断开一些边,使得所有红点联通,所有蓝点联通,且任意红点和任意蓝点不联通。
思路:我们求出所有红点lca1,所有蓝点lca2,如果所有蓝点不在lca1的子树中或者所有红点不在lca2的子树中,那么就是yes。
#include
#define pb push_back
using namespace std;
const int maxn = 2e5 + 10;
int f[maxn][20], dep[maxn], L[maxn], R[maxn], cnt;
vector<int> G[maxn], a, b;
void dfs(int u, int fa) {
dep[u] = dep[fa] + 1;
L[u] = ++cnt;
f[u][0] = fa;
for (int i = 1; i < 20; i++)
f[u][i] = f[f[u][i - 1]][i - 1];
for (auto v : G[u])
if (v != fa)
dfs(v, u);
R[u] = cnt;
}
int LCA(int x, int y) {
if (dep[x] < dep[y])
swap(x, y);
for (int i = 19; i >= 0; i--)
if (dep[f[x][i]] >= dep[y])
x = f[x][i];
if (x == y)
return x;
for (int i = 19; i >= 0; i--)
if (f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
return f[x][0];
}
int ok(int u, vector<int> tmp) {
for (auto v : tmp)
if (L[v] >= L[u] && R[v] <= R[u])
return 0;
return 1;
}
int main() {
int n, u, v, q, k1, k2, x;
scanf("%d", &n);
for (int i = 1; i < n; i++) {
scanf("%d%d", &u, &v);
G[u].pb(v);
G[v].pb(u);
}
dfs(1, 0);
scanf("%d", &q);
while (q--) {
scanf("%d%d", &k1, &k2);
a.clear();
b.clear();
int lca1 = 0, lca2 = 0, mx = 0, mn = 0;
for (int i = 1; i <= k1; i++) {
scanf("%d", &x), a.pb(x);
if (!mx || L[mx] < L[x])
mx = x;
if (!mn || L[mn] > L[x])
mn = x;
}
lca1 = LCA(mx, mn);
mx = 0, mn = 0;
for (int i = 1; i <= k2; i++) {
scanf("%d", &x), b.pb(x);
if (!mx || L[mx] < L[x])
mx = x;
if (!mn || L[mn] > L[x])
mn = x;
}
lca2 = LCA(mx, mn);
if (ok(lca1, b))
puts("YES");
else if (ok(lca2, a))
puts("YES");
else
puts("NO");
}
}
E. Third-Party Software - 2
题意:选择最少的区间,使得这些区间能够覆盖[1, m]
思路:原题,写过几次了。
#include
using namespace std;
const int maxn = 2e5 + 10;
struct node{
int x, y, id;
bool operator<(const node& t) const {
if (x == t.x)
return y > t.y;
return x < t.x;
}
} a[maxn];
priority_queue<int, vector<int>, greater<int> > q;
int main()
{
int n, m, mx = 0, p = 0, t;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d%d", &a[i].x, &a[i].y), a[i].id = i;
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n && mx < m; i++) {
if (a[i].x > p + 1) {
if (a[i].x > mx + 1)
break;
p = mx;
q.push(t);
i--;
}
else {
if (a[i].y > mx)
mx = a[i].y, t = a[i].id;
}
if (mx >= m)
q.push(t);
}
if (mx < m)
puts("NO");
else {
puts("YES");
printf("%d\n", q.size());
while (!q.empty()) {
printf("%d ", q.top());
q.pop();
}
}
}
F. Friendly Fire
题意:有 n 个怪兽,每个怪兽有攻击力 ai 和血量 bi,你可以选择两个怪兽,如果 ai >= bj,那么怪兽 i 能打死怪兽 j,让他们同时攻击对方(有可能两只怪兽同时死亡),使得死亡的怪兽的攻击力之和最大。
思路:我们对于每只怪兽,把它的 ai 权值更新到max型线段树中的 bi 点去,枚举每只怪兽 i,我们从线段树中查找 区间[1, b[i] ]的最大值mx,如果mx存在,那么更新答案 ans = max(ans, mx),如果mx >= b[i],那么 ans = max(ans, mx + a[i]),但是细节挺多,有可能查到的怪兽是 i ,这样是不合法的,怎么消除这个问题,留给你深思啦(这题我代码写的巨丑)。
#include
using namespace std;
const int maxn = 3e5 + 10, N = 1e9;
int mx[maxn * 35], cur[maxn * 35], ls[maxn * 35], rs[maxn * 35];
int a[maxn], b[maxn], cnt;
void up(int &o, int l, int r, int k, int v, int id, int opt) {
if (!o)
o = ++cnt;
if (l == r) {
if (opt)
mx[o] = v, cur[o] = id;
else if (mx[o] < v)
mx[o] = v, cur[o] = id;
return;
}
int m = (l + r) / 2;
if (k <= m)
up(ls[o], l, m, k, v, id, opt);
else
up(rs[o], m + 1, r, k, v, id, opt);
mx[o] = max(mx[ls[o]], mx[rs[o]]);
if (mx[ls[o]] == mx[o])
cur[o] = cur[ls[o]];
else
cur[o] = cur[rs[o]];
}
int qu(int o, int l, int r, int ql, int qr) { // 查询最大值下标
if (ql > qr)
return 0;
if (l >= ql && r <= qr)
return cur[o];
int m = (l + r) / 2, p = 0, tmp = 0;
if (ql <= m)
tmp = qu(ls[o], l, m, ql, qr);
if (a[tmp] > a[p])
p = tmp;
if (qr > m)
tmp = qu(rs[o], m + 1, r, ql, qr);
if (a[tmp] > a[p])
p = tmp;
return p;
}
map<int, int> mp, mp2;
struct node{
int a, b;
node (int t1, int t2) {a = t1, b = t2; }
bool operator<(const node& t)const {
return a > t.a;
}
};
vector<node> G[maxn];
int main()
{
int n, rt = 0, sz = 0;
scanf("%d", &n);
int ans = 0, t1 = 1, t2 = 2;
for (int i = 1; i <= n; i++) {
scanf("%d%d", &a[i], &b[i]);
if (!mp2[b[i]])
mp2[b[i]] = ++sz;
G[mp2[b[i]]].push_back(node(a[i], i));
up(rt, 1, N, b[i], a[i], i, 0);
}
for (int i = 1; i <= sz; i++)
sort(G[i].begin(), G[i].end());
for (int i = 1; i <= n; i++) {
int tmp = qu(rt, 1, N, 1, a[i]);
if (tmp == i) {
int v = 0;
int t = mp2[b[i]], ttt = 0;
if (G[t].size() > 1)
v = G[t][1].a, ttt = G[t][1].b;
up(rt, 1, N, b[i], v, ttt, 1);
}
int k = qu(rt, 1, N, 1, a[i]);
if (k) {
if (a[k] > ans)
ans = a[k], t1 = i, t2 = k;
if (a[k] >= b[i] && a[k] + a[i] > ans)
ans = a[k] + a[i], t1 = i, t2 = k;
}
if (tmp == i)
up(rt, 1, N, b[i], G[mp2[b[i]]][0].a, G[mp2[b[i]]][0].b, 1);
}
printf("%d\n%d %d\n", ans, t1, t2);
}
G. Akinator
题意:很绕,有 n 个人,有一个是小偷,要求你必须经过询问刚好 k 次,每次询问一堆人是否有小偷,找到这个小偷,同时求一个东西:如果你是第 m 次确定了第 i 个人的身份,那么这个人贡献的权值是:ai * m,我们要求最小的总的贡献sum / 总的ai,可以对ai进行排序。
思路:先对a数组排序,设d[l][r][k]为已经问了 k 次,再去确认区间 [l , r] 的人的身份的最小贡献,转移方程:dp[l][r][k] = min(d[ l ] [ i ][k + 1] + d[i + 1][ r ][k + 1]) (i <= i < r)
#include
#define ll long long
using namespace std;
int n, k;
ll d[101][101][101], a[101], sum, inf = 1e18;
ll dfs(int l, int r, int step) {
if (step > k)
return inf;
if (l == r)
return a[l] * step;
if (d[l][r][step])
return d[l][r][step];
d[l][r][step] = inf;
for (int i = l; i < r; i++)
d[l][r][step] = min(d[l][r][step], dfs(l, i, step + 1) + dfs(i + 1, r, step + 1));
return d[l][r][step];
}
int main() {
cin>>n>>k;
for (int i = 1; i <= n; i++)
cin>>a[i], sum += a[i];
if (ceil(log2(n)) > k)
return puts("No solution"), 0;
sort(a + 1, a + 1 + n);
ll ans = dfs(1, n, 0);
ll tmp = __gcd(ans, sum);
printf("%lld/%lld\n", ans / tmp, sum / tmp);
}
H. Missing Number
轩少
I. Painting a Square
题意:有一个大方形,你有一个小方形刷子,问最小需要刷移动多少次,能把大方形刷满。
思路:我们发现我们每次刷,肯定是贴着大方形转圈圈的刷是最优的,每次刷一圈,大方形的边长就会缩小 2 * 小方形边长,然后就水题啦。
#include
#define ll long long
using namespace std;
ll dfs(ll a, ll b) {
if (a <= b)
return a;
if (a <= b * 2)
return (a - b) * 3 + b;
return (a - b) * 4 + dfs(a - 2 * b, b);
}
int main()
{
ll a, b;
cin>>a>>b;
cout<<dfs(a, b) - b;
}
J. The Power of the Dark Side - 2
轩少
K. Deck Sorting
题意:给你一个字符串s,有两个空柱子,你按顺序依次把这些字符放到两个柱子上,最后把两个柱子拼接,问是否能形成一个相同的字符必连续的目标串。
思路:因为最多只有3种字符,我们枚举目标串的排列,然后判断其是否可行,怎么判断:假设目标串排列是:RGB,我们把R放在第一个柱子,B放在第二个柱子,那么G有可能在R下面,也有可能在B上面,我们依次枚举s,如果出现了B,那么我们标记一下第二个柱子不能放G了,接下来如果出现了G,因为有了标记,那么G只能放第一个柱子,我们判断R放完了没有,如果放完了那么放第一个柱子下面,如果没放完,那么不合法。
#include
using namespace std;
string s;
int ok(string targ) {
map<char, int> mp, mp2;
for (auto c : s)
mp[c]++;
for (int i = 0; i < targ.length(); i++)
mp2[targ[i]] = i + 1;
int flag = 0;
for (auto c : s) {
mp[c]--;
if (mp2[c] == 2) {
if (mp[targ[0]] && flag)
return 0;
}
else if (mp2[c] == 3) {
flag = 1;
}
}
return 1;
}
int main() {
cin>>s;
reverse(s.begin(), s.end());
if (ok("RGB") || ok("RBG") || ok("GRB") || ok("GBR") || ok("BRG") || ok("BGR"))
puts("YES");
else
puts("NO");
}
L. Inscribed Circle
轩少
M. Shlakoblock is live!
题意:有n个游戏,每个游戏有两个属性pi, vi,你可以选择一些游戏,使得vi + 1,使得所有的游戏 pi * vi 的总和除以总 vi 和最大。
思路:水题,先把游戏按照pi从大到小排序,我们从0开始选择枚举选择的游戏个数,然后把求出来的值更新到答案就行。
#include
#define pi pair
#define mk make_pair
using namespace std;
const int maxn = 1010;
struct node {
int p, v, id;
bool operator<(const node& t) const {
return p > t.p;
}
} a[maxn];
bool cmp(pi x, pi y) {
return 1ll * x.first * y.second > 1ll * y.first * x.second;
}
int main()
{
int T;
scanf("%d", &T);
while (T--) {
int n, sum = 0, res = 0, p = 0;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &a[i].p, &a[i].v);
a[i].id = i;
sum += a[i].v;
res += a[i].p * a[i].v;
}
sort(a + 1, a + 1 + n);
pi ans = mk(res, sum);
for (int i = 1; i <= n; i++) {
res += a[i].p;
sum++;
if (cmp(mk(res, sum), ans))
ans = mk(res, sum), p = i;
}
int d = __gcd(ans.first, ans.second);
printf("%d/%d\n", ans.first / d, ans.second / d);
printf("%d\n", p);
if (!p)
puts("");
for (int i = 1; i <= p; i++)
printf("%d%c", a[i].id, i == p ? '\n' : ' ');
}
}