2018.08.20 NOIp模拟赛
redbag的神题,真的很思维啊。。真的很工业啊。。
本博客大量借用redbag的博客,侵删。
第一题
给你\(~n~\)个数对,要求对于每个数对取出一个数计入答案,所有另一个数组成的集合大小为\(~n~\), 最大化答案并输出, 保证数据合法。\(~1 \leq n \leq 2.5 \times 10^5~, ~1 \leq x_i, y_i \leq 10 ^ 9\)
离散化数对之后对每个点对的 \(~x, ~y~\)之间连一条边,可以发现对于任意一条边都有一端作为贡献且另一端作为限制,考虑对边定向,\(~x~\)指向\(~y~\)表示\(~y~\)计入贡献且\(~x~\)为限制。因为数据合法,可以发现图是一个由__树__和__基环外套树__组成的森林。那么遍历每一个联通块:若当前是一颗树,则可以选择一个权值最大的点贡献\(~deg_u~\)次,其他所有点贡献\(~deg_u - 1~\)次;而对于基环外套树,因为边数和点数相等,所以每一个点都只能贡献\(~deg_u - 1~\)次。
code
#include<bits/stdc++.h>
#define x first
#define y second
#define mp make_pair
#define For(i, j, k) for(int i = j; i <= k; ++i)
#define Forr(i, j, k) for(int i = j; i >= k; --i)
#define Travel(i, u) for(int i = beg[u], v = to[i]; i; i = nex[i], v = to[i])
using namespace std;
inline int read() {
int x = 0, p = 1; char c = getchar();
for(; !isdigit(c); c = getchar()) if(c == '-') p = -1;
for(; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
return x *= p;
}
template<typename T> inline bool chkmin(T &a, T b) { return a > b ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return a < b ? a = b, 1 : 0; }
inline void File() {
freopen("s.in", "r", stdin);
freopen("s.out", "w", stdout);
}
typedef long long ll;
typedef pair<int, int> PII;
const int N = 25e4 + 1e2, M = N << 1;
int n, tot, ls[M], deg[M], siz[M], t, cnt, vis[M];
int e = 1, beg[M], nex[M << 1], to[M << 1];
vector<int> vec[M]; PII P[N];
inline void add(int x, int y) {
to[++ e] = y, nex[e] = beg[x], beg[x] = e, ++ deg[y];
to[++ e] = x, nex[e] = beg[y], beg[y] = e, ++ deg[x];
}
inline void dfs(int u, int f, int tag) {
vis[u] = tag, vec[tag].push_back(u), t += deg[u];
Travel(i, u) if (v != f && !vis[v]) dfs(v, u, tag);
}
int main() {
File();
n = read();
For(i, 1, n) {
P[i].x = read(), P[i].y = read();
ls[++ tot] = P[i].x, ls[++ tot] = P[i].y;
}
sort(ls + 1, ls + 1 + tot), tot = unique(ls + 1, ls + 1 + tot) - ls - 1;
For(i, 1, n) {
P[i].x = lower_bound(ls + 1, ls + 1 + tot, P[i].x) - ls;
P[i].y = lower_bound(ls + 1, ls + 1 + tot, P[i].y) - ls;
add(P[i].x, P[i].y);
}
For(i, 1, n) if (!vis[i])
++ cnt, t = 0, dfs(i, 0, cnt), siz[cnt] = t >> 1;
ll ans = 0;
For(i, 1, cnt) {
int sz = vec[i].size();
if (sz == siz[i])
for (auto v : vec[i]) ans += 1ll * (deg[v] - 1) * ls[v];
else {
int res = 0;
for (auto v : vec[i]) ans += 1ll * (deg[v] - 1) * ls[v], res = max(res, ls[v]);
ans += res;
}
}
printf("%lld\n", ans);
return 0;
}
第二题
数据规模\(~O(n ^ 2)~\)还可以套一个小\(~log~\),超级背包四合一,第一二问都可以直接\(~dp~\)。对于第三问,可以考虑把序列按一个奇怪的法则排序,按照作业本的课时数将作业本分成若干类。依次从小到大在每一类中取一本作业本,加入到数组里,直到所有的作业被取完,这样再跑背包,枚举第一符合条件的位置,输出其在之前出现过的次数就行了,而要完成这样的排序,可以用简单暴力的\(~multiset~\),也可以用常数小点的链表。对于第四问,题解太神了。
code
#include<bits/stdc++.h>
#define Set(a, b) memset(a, b, sizeof (a))
#define For(i, j, k) for(int i = j; i <= k; ++i)
#define Forr(i, j, k) for(int i = j; i >= k; --i)
using namespace std;
inline int read() {
int x = 0, p = 1; char c = getchar();
for(; !isdigit(c); c = getchar()) if(c == '-') p = -1;
for(; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
return x *= p;
}
template<typename T> inline bool chkmin(T &a, T b) { return a > b ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return a < b ? a = b, 1 : 0; }
inline void File() {
freopen("b.in", "r", stdin);
freopen("b.out", "w", stdout);
}
const int N = 5e3 + 10, inf = 0x3f3f3f3f;
int n, V, a[N], minn;
namespace Task1 {
int dp[N];
inline void Solve() {
Set(dp, 127), dp[0] = 0;
For(i, 1, n) Forr(j, V, a[i]) chkmin(dp[j], dp[j - a[i]] + 1);
minn = dp[V], printf("%.7lf ", 1.0 * V / minn);
}
}
namespace Task2 {
int dp1[N][N], dp2[N][N];
inline void Solve() {
Set(dp1, 127), Set(dp2, 127); dp1[0][0] = 0, dp2[n + 1][0] = 0;
For(i, 1, n) Forr(j, V, 0) {
if (j >= a[i]) chkmin(dp1[i][j], dp1[i - 1][j - a[i]] + 1);
chkmin(dp1[i][j], dp1[i - 1][j]);
}
Forr(i, n, 1) Forr(j, V, 0) {
if (j >= a[i]) chkmin(dp2[i][j], dp2[i + 1][j - a[i]] + 1);
chkmin(dp2[i][j], dp2[i + 1][j]);
}
int res = minn + 1 >> 1;
For(i, 1, n) For(j, 0, V) {
if (dp1[i][j] + dp2[i + 1][V - j] == minn && dp1[i][j] == res) {
printf("%d ", a[i]); return;
}
}
}
}
namespace Task3 {
int dp[N][N], tmp[N], cnt[N], pre[N], nex[N], A[N];
inline void Solve() {
For(i, 1, n) ++ cnt[a[i]], A[i] = a[i];
int tot = unique(A + 1, A + 1 + n) - A - 1;
For(i, 1, tot) {
pre[A[i]] = i == 1 ? A[tot] : A[i - 1];
nex[A[i]] = i == tot ? A[1] : A[i + 1];
}
int c = 0, now = A[1];
for (;;) {
if (c == n) break;
tmp[++ c] = now, -- cnt[now];
if (!cnt[now]) nex[pre[now]] = nex[now], pre[nex[now]] = pre[now];
now = nex[now];
}
Set(dp, 127), dp[0][0] = 0;
For(i, 1, n) Forr(j, V, 0) {
if (j >= tmp[i]) chkmin(dp[i][j], dp[i - 1][j - tmp[i]] + 1);
chkmin(dp[i][j], dp[i - 1][j]);
}
For(i, 1, n) {
++ cnt[tmp[i]];
if (dp[i][V] == minn) { printf("%d ", cnt[tmp[i]]); return; }
}
}
}
namespace Task4 {
int dp1[N][N], dp2[N][N], S1[N], S2[N];
int c1, c2, ans = inf, l = 1, r = 0;
inline bool check() {
For(i, 0, V) if (dp1[c1][i] + dp2[c2][V - i] == minn) return true;
return false;
}
inline void clear1() { Set(dp1, 127), dp1[0][0] = 0; }
inline void clear2() { Set(dp2, 127), dp2[0][0] = 0; }
inline void Solve() {
clear1(), clear2();
while (l <= n && r <= n) {
if (check()) {
chkmin(ans, a[r] - a[l]), ++ l;
if (!c1) {
clear1();
Forr(i, c2, 1) {
S1[++ c1] = S2[i];
For(st, 0, V) dp1[c1][st] = dp1[c1 - 1][st];
Forr(j, V, S1[c1]) chkmin(dp1[c1][j], dp1[c1 - 1][j - S1[c1]] + 1);
}
c2 = 0, clear2();
} -- c1;
} else {
++ r, S2[++ c2] = a[r];
For(st, 0, V) dp2[c2][st] = dp2[c2 - 1][st];
Forr(j, V, a[r]) chkmin(dp2[c2][j], dp2[c2 - 1][j - a[r]] + 1);
}
}
printf("%d\n", ans);
}
}
int main() {
File();
n = read(), V = read();
For(i, 1, n) a[i] = read();
sort(a + 1, a + 1 + n);
Task1::Solve();
Task2::Solve();
Task3::Solve();
Task4::Solve();
return 0;
}
第三题
给你一张\(~n~\)个点\(~m~\)条边的带权无向图,对于一条合法路径,你可以不用支付最贵的\(~k~\)条路,求最短路。\(~1 \leq n, m \leq 3000, ~0 \leq k \leq m, z \leq 10 ^ 9~\)。考虑枚举每一条边的权值\(~w_i~\),使所有边变成\(~max \{0, ~T_i - w_i\}~\), 跑最短路,最后的答案加上\(~k \times T_i~\),取最小值。至于这样为什么是对的,脑补一下一些情况就可以知道了。这个方法很套路,考场上根本想不到。。。
code
#include<bits/stdc++.h>
#define fir first
#define sec second
#define mp make_pair
#define For(i, j, k) for(int i = j; i <= k; ++i)
#define Forr(i, j, k) for(int i = j; i >= k; --i)
#define Travel(i, u) for(int i = beg[u], v = to[i]; i; i = nex[i], v = to[i])
using namespace std;
inline int read() {
int x = 0, p = 1; char c = getchar();
for(; !isdigit(c); c = getchar()) if(c == '-') p = -1;
for(; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
return x *= p;
}
inline void File() {
freopen("y.in", "r", stdin);
freopen("y.out", "w", stdout);
}
typedef long long ll;
typedef pair<ll, int> PII;
const int N = 3e3 + 10, M = N << 1;
const ll inf = 1e15;
int e = 1, beg[N], nex[M], to[M], vis[N], n, m, k, tot;
ll dis[N], w[M], ans, T[M], ls[M];
inline void add(int x, int y, ll z) {
to[++ e] = y, nex[e] = beg[x], beg[x] = e, T[e] = z;
to[++ e] = x, nex[e] = beg[y], beg[y] = e, T[e] = z;
}
inline ll dij(ll x) {
priority_queue<PII, vector<PII>, greater<PII> > Q;
For(i, 2, e) w[i] = max(0ll, T[i] - x);
For(i, 1, n) vis[i] = 0, dis[i] = inf;
dis[1] = 0, Q.push(mp(0, 1));
while (!Q.empty()) {
PII x = Q.top(); int u = x.sec; Q.pop();
if (!vis[u]) {
vis[u] = 1;
Travel(i, u) if(dis[v] > x.fir + w[i]) {
dis[v] = x.fir + w[i];
Q.push(mp(dis[v], v));
}
}
}
return dis[n] + k * x;
}
int main() {
File();
cin >> n >> m >> k;
For(i, 1, m) {
int x = read(), y = read(), z = read();
add(x, y, 1ll * z), ls[++ tot] = z;
}
sort(ls + 1, ls + 1 + tot), tot = unique(ls + 1, ls + 1 + tot) - ls - 1;
ll ans = dij(0);
For(i, 1, tot) ans = min(ans, dij(ls[i]));
printf("%lld\n", ans);
return 0;
}
至于今天的题目名字,连起来就是一个很美丽的名字了。