A.括号匹配
题意:问一个括号环,在某一点处剪开,是否能成为一个合法的括号序列。
题解:考虑左右括号数量是否相等即可。
证明:不会。
B. Oooooooo AAAAE
题解:二分图最小点权覆盖模板题,建议自行百度学习,一下子也很难讲清楚,但真的是模板题。
代码:
#include <bits/stdc++.h>
#define mem(sx,sy) memset(sx,sy,sizeof(sx))
#define pa pair<int, int>
typedef long long ll;
typedef unsigned long long ull;
const int MAXN = 1e5 + 100;
const int MAXM = 2e6 + 5;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const double PI = acos(-1);
using namespace std;
struct dinic {
struct edge {
int to, flow, nxt;
} edges[MAXM];
int n, m, cnt, st, en;
int head[MAXM], dis[MAXM], cur[MAXM];
void init(int _st, int _en, int _n) {
st = _st, en = _en, n = _n;
cnt = -1;
mem(head, -1);
}
void addedge(int u, int v, int w) {
// cout << " " << u << " " << v << " " << w << endl;
edges[++cnt] = { v, w, head[u] };
head[u] = cnt;
edges[++cnt] = { u, 0, head[v] };
head[v] = cnt;
}
int bfs() {
queue<int> Q;
mem(dis, 0);
dis[st] = 1;
Q.push(st);
while (!Q.empty()) {
int u = Q.front();
Q.pop();
if (u == en) return 1;
for (int i = head[u]; i != -1; i = edges[i].nxt) {
int v = edges[i].to, w = edges[i].flow;
if (!dis[v] && w) {
dis[v] = dis[u] + 1;
Q.push(v);
}
}
}
return dis[en] != 0;
}
int dfs(int u, int flow) {
int ret = flow, a;
if (u == en || flow == 0) return flow;
for (int &i = cur[u]; i != -1; i = edges[i].nxt) {
int v = edges[i].to;
if (dis[v] == dis[u] + 1 && (a = dfs(v, min(ret, edges[i].flow)))) {
edges[i].flow -= a;
edges[i ^ 1].flow += a;
ret -= a;
if (!ret) break;
}
}
if (ret == flow) dis[u] = 0;
return flow - ret;
}
int maxflow() {
int ans = 0;
while (bfs()) {
for (int i = 1; i <= n; i++) cur[i] = head[i];
ans += dfs(st, INF);
}
return ans;
}
} G;
int main() {
int n, m;
scanf("%d%d", &n, &m);
int st = 2 * n + 1;
int ed = st + 1;
G.init(st, ed, ed);
for (int i = 1, w; i <= n; i++) {
scanf("%d", &w);
G.addedge(n + i, ed, w);
}
for (int i = 1, w; i <= n; i++) {
scanf("%d", &w);
G.addedge(st, i, w);
}
while (m--) {
int u, v;
scanf("%d%d", &u, &v);
G.addedge(u, v+n, INF);
}
printf("%d\n", G.maxflow());
}
C. 给你一个666
题意:给你一个数组,每次给你两个数字,经过一系列的操作后,问你一个区间内的最大和最小值。
题解:给你 a , b a, b a,b,令 c c c为 a a a在二进制下的高3位乘 b b b在二进制下的低3位,令 d d d为 b b b在二进制下的高三位乘 a a a在二进制下的低3位, 即 c = ( a / 8 ) ∗ ( b % 8 ) , d = ( a % 8 ) ∗ ( b / 8 ) c=(a/8)*(b\%8),d=(a\%8)*(b/8) c=(a/8)∗(b%8),d=(a%8)∗(b/8),区间左端点 l = m i n ( a , b ) ∗ m i n ( c , d ) l=min(a,b)*min(c,d) l=min(a,b)∗min(c,d),右端点 r = m a x ( a , b ) ∗ m a x ( c , d ) , r=max(a,b)*max(c,d), r=max(a,b)∗max(c,d),然后区间最值询问可以用ST表或者线段树。
巨坑:阅读理解,左端点越界时按1处理,右端点越界时按n处理,也就是假如 l = r = n + 1 l=r=n+1 l=r=n+1,则也要使得 l = 1 , r = n l=1,r=n l=1,r=n。
代码:
#include <bits/stdc++.h>
#pragma warning(disable:4996);
#define mem(sx,sy) memset(sx,sy,sizeof(sx))
#define pa pair<int, int>
typedef long long ll;
typedef unsigned long long ull;
const int MAXN = 1e5 + 100;
const int MAXM = 2e6 + 5;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const double PI = acos(-1);
using namespace std;
int a[MAXN];
int dpmin[MAXN][20];
int dpmax[MAXN][20];
void RMQinit(int n) {
for (int i = 1; i <= n; i++) {
dpmin[i][0] = a[i];
dpmax[i][0] = a[i];
}
int m = int(log((double)n) / log(2.0));
for (int i = 1; i <= m; i++) {
for (int j = 1; j + (1 << i) - 1 <= n; j++) {
dpmin[j][i] = min(dpmin[j][i - 1], dpmin[j + (1 << (i - 1))][i - 1]);
dpmax[j][i] = max(dpmax[j][i - 1], dpmax[j + (1 << (i - 1))][i - 1]);
}
}
}
int Querymin(int l, int r) {
int k = int(log(double(r - l + 1)) / log(2.0));
return min(dpmin[l][k], dpmin[r - (1 << k) + 1][k]);
}
int Querymax(int l, int r) {
int k = int(log(double(r - l + 1)) / log(2.0));
return max(dpmax[l][k], dpmax[r - (1 << k) + 1][k]);
}
int main() {
int n, k;
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
RMQinit(n);
while (k--) {
int a, b;
scanf("%d%d", &a, &b);
int ah = a / 8, al = a % 8;
int bh = b / 8, bl = b % 8;
int c = ah * bl, d = al * bh;
int l = min(c, d) * min(a, b);
int r = max(c, d) * max(a, b);
l = max(1, l);
r = min(n, r);
printf("%d %d\n", Querymax(l, r), Querymin(l, r));
}
}
D. LiMn2O4的数学之路
题意:该公式为斐波那契额数列的通项公式,然后问你斐波那契额数列的第 n ( n < 1 0 9 ) n(n<10^9) n(n<109)项。
题解:矩阵快速幂裸题,建议自己百度学习。
E. Spinach和发牌姬
题意:大模拟。
题解:按照题意模拟即可,(我就剩这一道还没过了qaq
F. 猜球球
题意:有n个盒子,有个小球小球出现在每个盒子的概率为p[i],你现在可以问类似(小球是否在盒子{1,4,……,n}中?)(1-n的一个子集)的问题,问策略最优情况下,猜出小球所在盒子的猜测次数的数学期望。
官方题解:因为任意一次询问和回答,都可以确定其中一半的球球集合包含目标球,另一半则不包含目标球。然后再对包含目标球的球球集合进一步划分,直到包含目标球的集合里只包含一个球,就可以百分百确定了。这样就得到了一个决策树(二叉形状),二叉决策树根节点到每个叶子的路到都是期中一种情况的解决方案,显然深度就是询问次数。 则有:期望=∑(询问次数每种情况出现的概率)=∑(叶子对应的深度它出现在盒子里的概率)。 而我们知道:这个公式 ∑(深度*元素出现的概率 ) 与某种编码方案的编码长度期望公式 相同。询问次数的期望最小也就是编码长度的期望最小。而解决这个问题的经典方法就是——哈夫曼树。(https://blog.youkuaiyun.com/GreyBtfly/article/details/89456514)
一句话总结:构造一棵哈夫曼树,然后有 a n s = ∑ i = 1 n p [ i ] ∗ d e e p [ i ] ans = \sum\limits_{i=1}^{n}p[i]*deep[i] ans=i=1∑np[i]∗deep[i]
但我这里有段更加简短优美的代码:
#include <bits/stdc++.h>
#pragma warning(disable:4996);
#define mem(sx,sy) memset(sx,sy,sizeof(sx))
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const int MAXN = 5000 + 5;
const int MAXM = 2e6 + 5;
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
using namespace std;
#define pa pair<double, int>
vector<int> G[MAXN];
double a[MAXN];
double ans = 0;
void dfs(int cur, int d) {
if (G[cur].size() == 0) ans += a[cur] * d;
for (auto it : G[cur]) {
dfs(it, d + 1);
}
}
int main() {
int n;
scanf("%d", &n);
priority_queue<pa, vector<pa>, greater<pa> >Q;
for (int i = 1; i <= n; i++) {
scanf("%lf", &a[i]);
if (a[i] == 0) {
n--; i--; continue;
}
Q.push(pa(a[i], i));
}
int cnt = n;
while (Q.size() >= 2) {
pa t1 = Q.top(); Q.pop();
pa t2 = Q.top(); Q.pop();
pa np(t1.first + t2.first, ++cnt);
Q.push(np);
G[cnt].push_back(t1.second);
G[cnt].push_back(t2.second);
}
int root = Q.top().second;
dfs(root, 0);
printf("%.7lf\n", ans);
}
/*
3
0.5 0.25 0.25
4
0.3 0.3 0.2 0.2
*/
G. 似魔鬼的步伐
题意:你有 n n n块钱,初始能力 a n s = 0 ans=0 ans=0,你可以花一块钱使得 a n s = a n s + 1 ans=ans+1 ans=ans+1,或者花两块钱使得 a n s = 2 ∗ a n s + 2 ans=2*ans+2 ans=2∗ans+2,问 a n s ans ans最大值。
题解:贪心,花一个两块 比 花两个一块要赚,n为偶数的话一直花两块即可,n为奇数的话先花一个一块,然后一直花两块,数据范围较小,矩阵快速幂都不用。
H. 挑剔程度
题意:n个值,你可以最多去掉k个值,问剩下的最大值。
题解:温暖的签到题,排个序就可以了,注意k是可以大于n的。
I. 热狗树
题意:有一棵点权为0或1的树,问所有0的点到所有1的距离和以及所有1的点到所有0的距离和。
题解:树形dp,设 n u m [ 0 ] num[0] num[0]和 n u m [ 1 ] num[1] num[1]分别为点权为0或者1的点的个数,由dp可以得到 u u u号节点的 s i z [ 0 ] [ u ] siz[0][u] siz[0][u]和 s i z [ 1 ] [ u ] siz[1][u] siz[1][u]分别为 u u u节点子树点权为0和子树点权为1的点的个数,则对于某一个点u,其连着他爸爸的那条边的边权为w,则这条边对于答案的贡献为
a n s + = w ∗ ( s i z [ 0 ] ∗ ( n u m [ 1 ] − s i z [ 1 ] ) + s i z [ 1 ] ∗ ( n u m [ 0 ] − s i z [ 0 ] ) ) ans+=w*(siz[0]*(num[1]-siz[1])+siz[1]*(num[0]-siz[0])) ans+=w∗(siz[0]∗(num[1]−siz[1])+siz[1]∗(num[0]−siz[0]))
至于为什么你们自己稍微想一想应该就能理解了!(考虑一条边将一棵树分成了两个部分)记得取模。
代码:
#include <bits/stdc++.h>
#pragma warning(disable:4996);
#define mem(sx,sy) memset(sx,sy,sizeof(sx))
#define pa pair<int, int>
typedef long long ll;
typedef unsigned long long ull;
const int MAXN = 2e5 + 5;
const int MAXM = 2e6 + 5;
const int INF = 0x3f3f3f3f;
const double PI = acos(-1);
const int MOD = 998244353;
using namespace std;
vector<pa> G[MAXN];
int a[MAXN];
int siz01[MAXN];
int siz[2][MAXN];
ll ans = 0;
int n, sz[2] = { 0,0 };
void dfs1(int cur, int fa) {
siz[a[cur]][cur] = 1;
for (auto it : G[cur]) {
int v = it.first;
if (v == fa) continue;
dfs1(v, cur);
siz[0][cur] += siz[0][v];
siz[1][cur] += siz[1][v];
}
}
void dfs2(int cur, int fa) {
for (auto it : G[cur]) {
int v = it.first;
int w = it.second;
if (v == fa) continue;
ans += 1ll * siz[1][v] * (sz[0] - siz[0][v]) % MOD * w;
ans %= MOD;
ans += 1ll * siz[0][v] * (sz[1] - siz[1][v]) % MOD * w;
ans %= MOD;
dfs2(v, cur);
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
sz[a[i]]++;
}
for (int i = 1, u, v, w; i < n; i++) {
scanf("%d%d%d", &u, &v, &w);
G[u].emplace_back(v, w);
G[v].emplace_back(u, w);
}
dfs1(1, 0);
dfs2(1, 0);
printf("%lld\n", ans * 2 % MOD);
}
J. 流浪西邮之寻找火石碎片
题意:n件物品,每件有两个不同的货币的价格c1,c2,你可以使用任意一种货币购买它,和其价值v,你现在有两种货币a,b(对应c1,c2)你现在还能免费拿k件物品,问最大价值是多少。
考虑二维01背包,再加一维霸王餐,令 d p [ i ] [ x ] [ y ] [ j ] dp[i][x][y][j] dp[i][x][y][j]为第i件物品,花费x单位的第一种货币,y单位的第二种货币,用了j次霸王餐的最大值,则有暴力转移:
d p [ i ] [ x ] [ y ] [ j ] = m a x ( d p [ i − 1 ] [ x ] [ y ] [ j ] , d p [ i − 1 ] [ x − c 1 [ i ] ] [ y ] [ j ] + v [ i ] , d p [ i − 1 ] [ x ] [ y − c 2 [ i ] ] [ j ] + v [ i ] , d p [ i − 1 ] [ x ] [ y ] [ j − 1 ] + v [ i ] ) dp[i][x][y][j]=max(dp[i-1][x][y][j], \quad dp[i-1][x-c1[i]][y][j]+v[i], \quad dp[i-1][x][y-c2[i]][j]+v[i], \quad dp[i-1][x][y][j-1]+v[i] ) dp[i][x][y][j]=max(dp[i−1][x][y][j],dp[i−1][x−c1[i]][y][j]+v[i],dp[i−1][x][y−c2[i]][j]+v[i],dp[i−1][x][y][j−1]+v[i])
转移考虑清楚后就像01背包那样写就行了,数据范围不大,空间够用,不用优化,注意史诗级wa点:多组输入。
代码:
#include <bits/stdc++.h>
#pragma warning(disable:4996);
#define mem(sx,sy) memset(sx,sy,sizeof(sx))
#define pa pair<int, int>
typedef long long ll;
typedef unsigned long long ull;
const int MAXN = 1e5 + 100;
const int MAXM = 2e6 + 5;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const double PI = acos(-1);
using namespace std;
int dp[105][105][105][10];
int c1[105], c2[105], v[105];
int main() {
int n, m1, m2, k;
while (~scanf("%d%d%d%d", &n, &m1, &m2, &k)) {
for (int i = 1; i <= n; i++) {
scanf("%d%d%d", &c1[i], &c2[i], &v[i]);
}
int ans = 0;
mem(dp, 0);
for (int i = 1; i <= n; i++) {
for (int x = 0; x <= m1; x++) {
for (int y = 0; y <= m2; y++) {
for (int j = 0; j <= k; j++) {
dp[i][x][y][j] = dp[i - 1][x][y][j];
if (x >= c1[i]) {
dp[i][x][y][j] = max(dp[i][x][y][j], dp[i - 1][x - c1[i]][y][j] + v[i]);
}
if (y >= c2[i]) {
dp[i][x][y][j] = max(dp[i][x][y][j], dp[i - 1][x][y - c2[i]][j] + v[i]);
}
if (j > 0) {
dp[i][x][y][j] = max(dp[i][x][y][j], dp[i - 1][x][y][j - 1] + v[i]);
}
ans = max(dp[i][x][y][j], ans);
}
}
}
}
printf("%d\n", ans);
}
}