2018.08.21高二互测

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;
}

​ 至于今天的题目名字,连起来就是一个很美丽的名字了。

posted @ 2018-08-22 08:23  LSTete  阅读(176)  评论(0编辑  收藏  举报