redbag r e d b a g 的神题直接踩爆我了
T1 ![](https://i-blog.csdnimg.cn/blog_migrate/e520583173c0a9e504141907b800e740.png)
这个题看起来比较简单但是模型转化还是比较难想的
考场上我大概想到把答案记为总和减去选定的 n n 个互不相同的宽
但是没有跳出这个模型 题解的思想巧妙多了
把每个权值离散后看作一个点,每对限制关系记为一条边
题目就可以转化为在每一条边上选择一个端点,不可以重复选择
求最小权值和
由于答案保证有解 所以最后得到的多个联通块要么是树,要么是基环树
如果是基环树的话,环上的权值是一定要选的,如果是树,就把最大的权值去掉
这些操作我们可以用一个并查集来维护
维护一个集合的最大值和集合内权值和,以及当前集合是不是基环树
最后算答案的时候每个联通块如果不是基环树 就减去联通块最大权值
这样子就做完了 复杂度 瓶颈在离散化的排序上
#include<bits/stdc++.h>
using namespace std;
template<class T> inline bool chkmin(T &_, T __) {return _ > __ ? _ = __, 1 : 0;}
typedef long long ll;
const int N = 5e5 + 10;
int a[N], b[N], tmp[N];
int n, cnt, s, t, vis[N];
ll all;
struct Union_Set {
int fa[N], size[N], mx[N]; ll sum[N];
void clear() {
for(int i = 1; i <= cnt; ++ i)
fa[i] = mx[i] = i, sum[i] = tmp[i], size[i] = 1;
}
int find(int x) {
return fa[x] = x == fa[x] ? x : find(fa[x]);
}
void merge(int x, int y) {
x = find(x), y = find(y);
-- size[x];
if(x == y) return;
sum[x] += sum[y];
size[x] += size[y];
mx[x] = max(mx[x], mx[y]);
fa[y] = x;
}
}T;
int main() {
#ifndef ONLINE_JUDGE
freopen("s.in", "r", stdin);
freopen("s.out", "w", stdout);
#endif
scanf("%d", &n);
for(int i = 1; i <= n; ++ i) {
scanf("%d%d", &a[i], &b[i]);
tmp[++ cnt] = a[i], tmp[++ cnt]= b[i];
all += a[i] + b[i];
}
sort(tmp + 1, tmp + cnt + 1);
cnt = unique(tmp + 1, tmp + cnt + 1) - tmp - 1;
T.clear();
for(int i = 1; i <= n; ++ i) {
a[i] = lower_bound(tmp + 1, tmp + cnt + 1, a[i]) - tmp;
b[i] = lower_bound(tmp + 1, tmp + cnt + 1, b[i]) - tmp;
T.merge(a[i], b[i]);
}
// cout << T.sum[5] << ' ' << T.mx[5] << endl;
for(int i = 1; i <= cnt; ++ i)
if(!vis[T.find(i)]) {
vis[T.find(i)] = 1;
if(T.size[T.find(i)] >= 1)
all -= T.sum[T.find(i)] - tmp[T.mx[T.find(i)]];
else
all -= T.sum[T.find(i)];
}
cout << all << endl;
return 0;
}
T2![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/e77aa2012713ccafd660c340f8957c6d.png)
这题不能说巧妙了… 简直是神仙题… 昨天晚上要走的时候才调出来 还是对着代码打的
坑点也蛮多的 反正只有我这种菜逼会被坑
第一问 设状态
f[i][j]
f
[
i
]
[
j
]
表示选前
i
i
个物品达到的容量的最小物品数 直接转移就好了
二维背包忘的差不多了…搞得我按一维的打一直调不出来
第二问 对物品排序后,正着做一遍倒着做一遍,中位数对于前一半是向上取整,后一半是向下取整,只要 f1[i][j]+f2[i+1][v−j]==ans1 f 1 [ i ] [ j ] + f 2 [ i + 1 ] [ v − j ] == a n s 1 就可以用 i i 更新答案
第三问 二分一个答案,把物品数量相同的放一起,变成一个多重背包,然后把物品数限制在二分的那个答案那里就可以了,这里实际上复杂度并不高,因为如果它要让你的物品种类多,那答案的范围就会小,如果答案范围大,物品种类数就会小,所以是跑不满的
第四问 最神仙的就是这一问了…看了 LSTete L S T e t e 的代码才勉强理解
就是之前考过一道贪玩蓝月的题,就是这种思路,用一个栈模拟队列来维护一个支持动态删减物品的背包,首先用双栈模拟队列的操作,进队的话就把元素都放到第一个栈,出队的话如果第二个栈有元素,那么就弹出第二个栈的栈顶,没有的话就第一个栈所有元素压进第二个栈,再弹出栈顶,这样就完成了一个队列的基本操作,我们考虑用这个模拟的队列来维护这个背包,添加物品比较简单,做一遍 O(v) O ( v ) 的 dp d p 就好了,因为 f[i−1][j] f [ i − 1 ] [ j ] 就是 f[i][j] f [ i ] [ j ] 删去第 i i 个物品后的状态,这样子直接删的转移是的,如果是把第一个栈的元素全部放到第二个栈中,每压过去一个元素对于这个东西做一个 dp d p ,最后复杂度看不懂…..反正能过就对了
Codes
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3 + 10;
int f1[N][N], f2[N][N];
int n, v, a[N], fuck;
namespace Sub1 {
void Solve() {
memset(f1, 0x3f, sizeof(f1));
f1[0][0] = 0;
for(int i = 1; i <= n; ++ i)
for(int j = 0; j <= v; ++ j) {
f1[i][j] = f1[i - 1][j];
if(j >= a[i]) f1[i][j] = min(f1[i][j], f1[i - 1][j - a[i]] + 1);
}
printf("%.12f ", 1.0 * v / (fuck = f1[n][v]));
}
}
namespace Sub2 {
void Solve() {
memset(f2, 0x3f, sizeof(f2));
f2[n + 1][0] = 0;
for(int i = n; i >= 1; -- i)
for(int j = 0; j <= v; ++ j) {
f2[i][j] = f2[i + 1][j];
if(j >= a[i]) f2[i][j] = min(f2[i][j], f2[i + 1][j - a[i]] + 1);
}
for(int i = 1; i <= n; ++ i)
for(int j = 0; j <= v; ++ j)
if(f1[i][j] + f2[i + 1][v - j] == f1[n][v] && f1[i][j] == (f1[n][v] + 1) / 2)
return void(printf("%d ", a[i]));
}
}
namespace Sub3 {
int f3[N], tmp[N], cnt[N], num;
bool check(int x) {
memset(f3, 0x3f, sizeof(f3));
f3[0] = 0;
for(int i = 1; i <= num; ++ i)
for(int k = min(cnt[i], x); k >= 1; -- k)
for(int j = v; j >= tmp[i]; -- j)
f3[j] = min(f3[j], f3[j - tmp[i]] + 1);
return f3[v] == f1[n][v];
}
void Solve() {
for(int i = 1; i <= n; ++ i) tmp[i] = a[i];
num = unique(tmp + 1, tmp + n + 1) - tmp - 1;
for(int i = 1; i <= n; ++ i)
++ cnt[lower_bound(tmp + 1, tmp + num + 1, a[i]) - tmp];
int l = 1, r = 0, ans;
for(int i = 1; i <= num; ++ i) r = max(r, cnt[i]);
while(l <= r) {
int mid = (l + r) >> 1;
if(check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d ", ans);
}
}
namespace Sub4 {
int f1[N][N], f2[N][N], Sta1[N], Sta2[N], top1, top2;
int ans = 1e9, l = 1, r = 0;
bool Check() {
for(int i = 0; i <= v; ++ i)
if(f1[top1][i] + f2[top2][v - i] == fuck)
return true;
return false;
}
void Clear1() {memset(f1, 0x3f, sizeof(f1)); f1[0][0] = 0;}
void Clear2() {memset(f2, 0x3f, sizeof(f2)); f2[0][0] = 0;}
void Solve() {
Clear1(), Clear2();
//cout << endl;
while(l <= n && r <= n) {
//cout << l << ' ' << r << endl;
if(Check()) {
ans = min(ans, a[r] - a[l]), ++ l;
if(!top2) {
Clear2();
for(int i = top1; i >= 1; -- i) {
Sta2[++ top2] = Sta1[i];
for(int j = 0; j <= v; ++ j) f2[top2][j] = f2[top2 - 1][j];
for(int j = v; j >= Sta2[top2]; -- j) f2[top2][j] = min(f2[top2][j], f2[top2][j - Sta2[top2]] + 1);
}
Clear1(); top1 = 0;
}
-- top2;
}
else {
Sta1[++ top1] = a[++ r];
for(int j = 0; j <= v; ++ j) f1[top1][j] = f1[top1 - 1][j];
for(int j = v; j >= Sta1[top1]; -- j) f1[top1][j] = min(f1[top1][j], f1[top1][j - Sta1[top1]] + 1);
// for(int j = 0; j <= v; ++ j) cout << top1 << ' ' << j << ' ' << f1[top1][j] << endl;
}
}
printf("%d\n", ans);
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("b.in", "r", stdin);
freopen("b.out", "w", stdout);
#endif
scanf("%d%d", &n, &v);
for(int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
Sub1::Solve(); Sub2::Solve(); Sub3::Solve(); Sub4::Solve();
return 0;
}
T3![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/81acf83da05c3b4df42f33738fc42794.png)
这个题目也是比较巧妙了…
首先枚举需要我们花费最小的边,假设权值为 t t
这样子我们做最短路的时候每条边权值都减去,小于 0 0 就设为
把得到的最短路加上 k×t k × t ,取其中答案的最小值即可
为什么这样做是对的 我们分类讨论一下
当经过的边小于 k k 的时候,由于取的是最小值,显然是不会对答案有影响的
因为枚举的时候,这样子最优的答案已经被记录下来了
当经过的边大于 k k 的时候 如果第 k k 条边的权值 那么可以让更大来让总权值更小,即此时一定不会比最后的答案更优,所以这样子做 m m 次就好了
时间复杂度 O(nmlog(n+m)) O ( n m l o g ( n + m ) )
#include<bits/stdc++.h>
#define x first
#define y second
#define mp make_pair
#define rel(x) (x < 0 ? 0 : x)
using namespace std;
typedef long long ll;
typedef pair<ll, int> PII;
template<class T> inline bool chkmin(T &_, T __) {return _ > __ ? _ = __, 1 : 0;}
const int N = 3000 + 10;
int to[N << 1], head[N], nxt[N << 1], v[N << 1], e;
int n, m, k, tmp[N], cnt;
ll dis[N], ans = 1e18;
void add(int x, int y, int z) {
to[++ e] = y; nxt[e] = head[x]; head[x] = e; v[e] = z;
}
ll dijistra(int x) {
priority_queue<PII, vector<PII>, greater<PII> > q;
for(int i = 1; i <= n; ++ i) dis[i] = 1e18;
q.push(mp(dis[1] = 0, 1));
while(!q.empty()) {
PII k = q.top(); q.pop();
if(k.x > dis[k.y]) continue;
for(int i = head[k.y]; i; i = nxt[i])
if(chkmin(dis[to[i]], dis[k.y] + rel(v[i] - x)))
q.push(mp(dis[to[i]], to[i]));
}
return dis[n];
}
int main() {
#ifndef ONLINE_JUDGE
freopen("y.in", "r", stdin);
freopen("y.out", "w", stdout);
#endif
int x, y, z;
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= m; ++ i) {
scanf("%d%d%d", &x, &y, &z);
add(x, y, z), add(y, x, z);
tmp[++ cnt] = z;
}
sort(tmp + 1, tmp + cnt + 1);
cnt = unique(tmp + 1, tmp + cnt + 1) - tmp - 1;
for(int i = 0; i <= cnt; ++ i)
ans = min(ans, dijistra(tmp[i]) + 1ll * tmp[i] * k);
printf("%lld\n", ans);
//cerr << 1.0 * clock() / CLOCKS_PER_SEC << endl;
return 0;
}
总结一下还是自己太菜了吧…只会写一些自己知道的简单套路,遇到这种题目做不出改的也慢,以后尽量多思考一会儿,在实在想不出的情况再去看题解,不要为了速度浪费了效率…然后就是改题的时候还是专心点吧,一会儿做点其他的事完全改不进题目…