A BBQ Easy
题目大意:
史努克在参加一个BBQ派对, 他要准备N组食物, 他有2N的食材, 需要两两组成一个食物, 食物的价值是两食材中较小的那个。 问最大总价值是多少
解题思路:
将其从小到大排序之后,计算奇数位置的和即可
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
int a[maxn];
int n;
int main() {
cin >> n;
n <<= 1;
for (int i = 1; i <= n; i++)
cin >> a[i];
sort (a + 1, a + n + 1);
int res = 0;
for (int i = 1; i <= n; i++) {
if (i & 1)
res += a[i];
}
cout << res << endl;
}
B - Mysterious Light
题目大意:
高桥くん有一个边长为N的三枚镜子构成的正三角形, 顶点为a, b, c。他有一个超级步枪,放在AB段的P点上,使得AP=X。并沿着平行于BC的方向发射一道光。
光以直线传播,以镜子的形式反射,但是有一个特殊的地方:它会被自己的轨迹反射,当光回到步枪的时候,光被吸收。
光的路径总长度是多少?
解题思路:
每次反射可以看成是在一个平行四边形内折射,所以直接递归处理即可
递归终止条件:恰好能够折射到一个端点
左图是折射之后仍然要进行递归,可以发现递归之后仍是平行四边形
右图是这射之后不需要进行递归了,可以直接返回
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
ll N, X, ans;
void dfs(ll len1, ll len2) { //op递归种类,len1步长,len2要走的长度
ll num = len2 / len1;
ans += (2 * num - 1) * len1;
if (len2 % len1 == 0) return;//终止条件
ans += len1;
dfs(len2 % len1, len1);
}
int main() {
cin >> N >> X;
ans += N;
dfs(min(X, N - X), max(X, N - X));
cout << ans << endl;
}
C - Shorten Diameter
题目大意:
经过最少的删点操作,使树的直径不超过k,并输出最少操作数目
解题思路:
若k是偶数,则遍历每个点为根节点,统计超过深度k/2的个数有多少,取最小即可
若k是奇数,则遍历每一条边,然后分别遍历边两端的节点,统计超过深度k/2的个数有多少即可
AC代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e3 + 10;
int n, k;
vector <int> G[maxn];
int res = 0, ans = 0, tmp1 = 0, tmp2 = 0;
void dfs(int u, int f, int d) {
for (auto v : G[u]) {
if (v == f) continue;
dfs(v, u, d + 1);
}
if (d > k / 2) res++;
}
int main() {
int u, v;
cin >> n >> k;
for (int i = 1; i < n; i++) {
cin >> u >> v;
G[u].push_back(v), G[v].push_back(u);
}
ans = n + 1;
if (!(k & 1))
for (int i = 1; i <= n; i++) {
res = tmp1 = tmp2 = 0;
dfs(i, 0, 0);
ans = min(ans, res);
}
else {
for (int i = 1; i <= n; i++) {
for (auto v : G[i]) {
res = 0;
dfs(v, i, 0);
dfs(i, v, 0);
ans = min(ans, res);
}
}
}
cout << ans << endl;
}
D - Arrays and Palindrome
题目大意:
已知一个重排之后的a序列,询问b序列
- b序列全为正整数,且之和等于a的序列之后
- 要求存在序列满足,前 a 1 a_1 a1个字符为回文串,随后 a 2 a_2 a2个字符为回文串…
- 前 b 1 b_1 b1个字符为回文串,随后 b 2 b_2 b2个字符为回文串…
- 那么该序列全为相同的字符
解题思路:
白嫖我队友的思路以及图片,队友博客
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxm = 1e5 + 10;
const int maxn = 1e5 + 10;
int n, m, cnt, len;
int a[maxm], b[maxn];
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++)
cin >> a[i];
int num = 0, k1 = 0, k2 = 0;
for (int i = 1; i <= m; i++)
if (a[i] & 1)
num++, k2 = k1, k1 = i;
if (m == 1) {
if (a[1] == 1) cout << a[1] << endl << 1 << endl << a[1] << endl;
else cout << a[1] << endl << 2 << endl << n - 1 << " " << 1 << endl;
return 0;
}
if (num > 2) {
cout << "Impossible" << endl;
return 0;
}
else if (num == 2)//将奇数个放在头尾
swap(a[k1], a[1]), swap(a[k2], a[m]);
else if (num == 1)
swap(a[k1], a[1]);
for (int i = 1; i <= m; i++)
cout << a[i] << " ";
cout << endl;
if (a[m] == 1) cout << m - 1 << endl;
else cout << m << endl;
cout << a[1] + 1 << " ";
for (int i = 2; i < m; i++)
cout << a[i] << " ";
if (a[m] - 1 != 0) cout << a[m] - 1 << endl;
else cout << endl;
}
E BBQ Hard
题目大意:
n
n
n个数,求
∑
i
=
1
j
=
n
∑
j
=
i
+
1
n
C
a
i
+
b
i
+
a
j
+
b
j
a
i
+
a
j
\sum_{i=1}^{j=n}\sum_{j=i+1}^{n}C_{a_i+b_i+a_j+b_j}^{a_i+a_j}
∑i=1j=n∑j=i+1nCai+bi+aj+bjai+aj的和,对1e9+7取模
解题思路:
众所周知:
C
x
+
y
x
C_{x+y}^x
Cx+yx代表
(
0
,
0
)
(0,0)
(0,0)到
(
x
,
y
)
(x,y)
(x,y)的方案数(然而我并不知道 )
所以:
C
a
i
+
b
i
+
a
j
+
b
j
a
i
+
a
j
C_{a_i+b_i+a_j+b_j}^{a_i+a_j}
Cai+bi+aj+bjai+aj代表
(
−
a
i
,
−
a
j
)
(-a_i,-a_j)
(−ai,−aj)到
(
b
i
,
b
j
)
(b_i,b_j)
(bi,bj)的方案数
然后dp即可,最后去除掉
(
−
a
i
,
−
a
i
)
(-a_i,-a_i)
(−ai,−ai)到
(
b
i
,
b
i
)
(b_i,b_i)
(bi,bi)的方案数,再除2即可
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 2e5 + 10;
const ll maxm = 4e3 + 100;
const ll maxv = 2e3 + 10;
const ll mod = 1e9 + 7;
ll dp[maxm][maxm], ans;
ll a[maxn], b[maxn];
ll fac[maxm << 1];
ll qpow(ll a, ll b) {
ll res = 1;
a %= mod;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void init() {
fac[1] = 1;
for (ll i = 2; i < 2 * maxm; i++)
fac[i] = fac[i - 1] * i % mod;
}
ll C(ll n, ll m) {
return fac[n] * qpow(fac[n - m], mod - 2) % mod * qpow(fac[m], mod - 2) % mod;
}
ll N;
int main() {
init();
cin >> N;
for (ll i = 1; i <= N; i++) {
cin >> a[i] >> b[i];
dp[maxv - a[i]][maxv - b[i]]++;
}
for (ll i = 1; i <= 2 * maxv; i++)
for (ll j = 1; j <= 2 * maxv; j++) {
dp[i][j] = (dp[i][j] + dp[i - 1][j] + dp[i][j - 1]) % mod;
}
for (ll i = 1; i <= N; i++) {
ans = (ans + dp[maxv + a[i]][maxv + b[i]]) % mod;
ans = (ans - C(2 * (a[i] + b[i]), 2 * a[i]) + mod) % mod;
}
ans = ans * qpow(2, mod - 2) % mod;
cout << ans << endl;
}
F - Wide Swap
题目大意:
给一个元素集合为
{
1
,
2
,
.
.
.
,
N
}
(
1
≤
N
≤
5
e
5
)
\{1,2,...,N\}(1\le N \le 5e5)
{1,2,...,N}(1≤N≤5e5)的排列P,当有
i
i
i,
j
j
j
(
1
≤
i
<
j
≤
N
)
(1\le i < j \le N)
(1≤i<j≤N)满足
j
−
i
≥
K
j-i \ge K
j−i≥K
(
1
≤
K
≤
N
−
1
)
(1\le K \le N-1)
(1≤K≤N−1)且
∣
P
i
−
P
j
∣
=
=
1
|P_i-P_j|==1
∣Pi−Pj∣==1,时可以交换
P
i
P_i
Pi和
P
j
P_j
Pj
求:可能排列中字典序最小的排列
解题思路:
有个非常妙的思路,就是将下标和值进行对换,关于新的下标相邻大于等于K即可交换,求出最后的序列即为答案
首先证明一个定理:
对于
∣
i
−
j
∣
<
K
|i-j|<K
∣i−j∣<K,
i
<
j
i<j
i<j,
p
i
<
p
j
p_i<p_j
pi<pj则进行交换之后得到的最终序列
p
i
′
<
p
j
′
p_i^{'}<p_j^{'}
pi′<pj′(p是原数组)
经过对换之后,
i
i
i始终在
j
j
j前,因为对应的下标为
p
i
′
p_i^{'}
pi′,
p
j
′
p_j^{'}
pj′而下标中
p
i
′
<
p
j
′
p_i^{'}<p_j^{'}
pi′<pj′
证毕
然后具体思想是看这篇博客的,就不再赘述了
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 10;
const int inf = 0x3f3f3f3f;
int n, k;
int p[maxn], mx[maxn << 2], ans[maxn];
#define ls rt << 1
#define rs rt << 1 | 1
void pushup(int rt) {
mx[rt] = p[mx[ls]] > p[mx[rs]] ? mx[ls] : mx[rs];
}
void build(int lc, int rc, int rt) {//线段树存储的是区间最大值的下标
if (lc == rc) {
mx[rt] = lc;
return;
}
int mi = (lc + rc) >> 1;
build(lc, mi, ls);
build(mi + 1, rc, rs);
pushup(rt);
return;
}
void del(int lc, int rc, int rt, int x) {
if (lc == rc) {
mx[rt] = 0;//删除该值产生影响
return;
}
int mi = (lc + rc) >> 1;
if (x <= mi) del(lc, mi, ls, x);
else del(mi + 1, rc, rs, x);
pushup(rt);
return;
}
int query(int lc, int rc, int rt, int L, int R) {
if (R < lc || rc < L) return 0;
if (L <= lc && rc <= R) return mx[rt];
int mi = (lc + rc) >> 1;
int u = query(lc, mi, ls, L, R), v = query(mi + 1, rc, rs, L, R);
return p[u] > p[v] ? u : v;
}
int inq[maxn];
priority_queue <int> que;//默认从大到小
void check(int id) {
if (inq[id]) return;
if (query(1, n, 1, id - k + 1, id + k - 1) == id)
//因为是反过来遍历,所以如果当前所在区间id是最大的,那么就说明入度为0
que.push(id), inq[id] = 1;
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++)
cin >> p[i];
p[0] = -inf;
build(1, n, 1);
for (int i = 1; i <= n; i++) check(i);
for (int i = n; i >= 1; i--) {
int u = que.top(); que.pop();
ans[u] = i;
//即[u-k+1,u+k-1]内没有比p[u]大的,处理相对位置关系(又是大根堆,大的位置先出来,为了最小)所以从后往前赋值
del(1, n, 1, u);
int pos;
//就跟拓扑排序过程一摸一样,检查删除之后有无入度为0的点
if ((pos = query(1, n, 1, u - k + 1, u - 1))) check(pos);
if ((pos = query(1, n, 1, u + 1, u + k - 1))) check(pos);
}
for (int i = 1; i <= n; i++)
cout << ans[i] << endl;
return 0;
}