ZR国庆Round2解题报告

心路历程

预计得分:100 + 10 - 20 + 10 = 120

实际得分:100 + 0 + 10 = 110

感觉这场打的挺稳的。开场秒掉A题,写+调差不多1h

然后刚T3暴力,刚完还有2h左右。。然后,,这时候我zz的选择去打T2的暴力,然而T2暴力真的不是一般的难写。。

终于又花了1h打完T2暴力,又打了打\(n <= 10\)的表,感觉稳的一批。

然后开始跑\(n = 11\)的,结果到比赛结束也没跑出来qwq。。

离比赛结束还有5min的时候发现T3跟K无关又少拿了20

下午看成绩的时候发现T2 \(n = 1\)的点玩错了完美爆零。。。。。。。

如果。。。再给我20min让我打完T2的表、、、、

如果。。。再给我15min让我打完T3的优化版暴力。。。。

好像就win了啊可惜没如果。。。。。

T1

单调队列随便做。。。

维护最大最小值,贪心的pop一下就好了。。

因为数据是随机的,所以单调队列里的元素不会很多,deque维护一下。。

#include<bits/stdc++.h>
#define LL long long 
using namespace std;
const int MAXN = 2e7 + 10;
inline int read() {
	char c = getchar(); int x = 0, f = 1;
	while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
	while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}
struct Node {
	int val, pos;
}; 
int N, K, seed, l, r; 
deque<Node> qmx, qmn;
int main() {
	N = read(); K = read();
	seed = read(); l = read(), r = read();
	LL ans = 0, pre = 0;
	for(int i = 1; i <= N; i++) {
		int a = seed % (r - l + 1) + l;
        seed = (13331ll * seed + 23333) % 1000000007;
		while(!qmx.empty() && a > qmx.back().val) qmx.pop_back();
		while(!qmn.empty() && a < qmn.back().val) qmn.pop_back();
		//cout << a << endl;
		qmx.push_back((Node) {a, i});
		qmn.push_back((Node) {a, i});	
		while(!qmx.empty() && !qmn.empty() && (1ll * qmx.front().val > 1ll * qmn.front().val * K) ) {
			if(qmx.front().pos < qmn.front().pos) pre = qmx.front().pos, qmx.pop_front();
			else pre = qmn.front().pos, qmn.pop_front();
		}
		if(!qmx.empty() && !qmn.empty()) ans += i - pre;
	}
	cout << ans;
	return 0;
}
/*
20000000 6
1234 4321 8765

*/

T2

这题是真神仙题啊。。。

直接说标算吧。

\(f[i][j]\)表示\(i\)个节点的无根树,最大深度至多为\(j\)的方案数,

\(g[i][j]\)表示\(i\)个节点的无根树,最大最大深度恰好为\(j\)的方案数,显然\(g[i][j] = f[i][j] - f[i][j - 1]\)

那么\(f[i][j] = i * \sum_{k = 0}^i \frac{f[i - k][j]}{i - k} * f[k][j - 1] * C_{n - 2}^{k - 1}\)

一个公式。。包含了无数个Trick。。Orz xudyh。

首先把无根树计数变成有根树计数,然后枚举与根节点相连的编号最小的点\(k\)

前面要除掉\(i - k\)的原因是因为此时我们不清楚根节点

最后\(C_{n - 2}^{k - 1}\)的意思是我们钦定了根节点和它相连的编号最小的点之后的答案

那么统计答案的时候

  • 如果直径是\(2j + 1\),那么答案为

\[\sum g[k][j] * g[N - k][j] * C_{n - 1}^{k - 1} \]

这个应该比较好理解,就是左右分别算一算。

  • 如果直径是\(2j\), 答案为

\(g[N][j] - \sum g[k][j - 1] * f[N - k][j - 1] * C_n^k\)

这个就比较有意思了,解释一下

现在我们钦定了一个根节点,只需要统计以它为中心的答案

\(g[n][j]\)表示的是深度最大为\(j\)的方案数,但是这里面会有一些不满足答案(只有一条长为\(j\)的链)

考虑减去不满足条件的

枚举两棵子树,\(g[k][j - 1]\)保证了其中的一个满足条件,另一个不满足条件的方案就是从根节点开始深度至多为\(j - 1\)的方案

标算代码看不懂。。。自己写的调不出来。。咕咕咕。。

#include<bits/stdc++.h>
#define chmax(a, b) (a = (a < b ? b : a))
#define chmin(a, b) (a = (a < b ? a : b))
//#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
using namespace std;
const int MAXN = 501;
// char buf[1 << 21], *p1 = buf, *p2 = buf;
inline int read() {
	char c = getchar(); int x = 0, f = 1;
	while(c < '0' || c > '9') {if(c == '-')f =- 1; c = getchar();}
	while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}
int N, M, f[MAXN][MAXN], g[MAXN][MAXN], C[MAXN][MAXN], mod;
int fastpow(int a, int p) {
	int base = 1;
	while(p) {
		if(p & 1) base = 1ll * a * base % mod;
		a = 1ll * a * a % mod; p >>= 1;
	}
	return base;
}
int inv(int x) {
	return fastpow(x, mod - 2);
}
main() {
	N = read(); mod = read();
	C[1][1] = 1;
	for(int i = 2; i <= N; i++) {
		C[i][0] = C[i][i] = 1;
		for(int j = 1; j <= N; j++) C[i][j] = (C[i - 1][j - 1] + C[i][j - 1]) % mod;
	}
	for(int i = 0; i <= N; i++) f[0][i] = g[0][i] = f[1][i] = g[1][i] = 1;
	for(int i = 1; i <= N; i++) {
		f[i][1] = i; 
		for(int j = 2; j <= N; j++) {
			int sum = 0;
			for(int k = 0; k <= i; k++) 
				(sum += 1ll * f[i - k][j] * inv(i - k) % mod * f[k][j - 1] % mod * C[N - 2][k - 1] % mod) %= mod;
			if(!f[i][j]) f[i][j] = 1ll * i * sum % mod;
		}
		for(int j = 0; j <= N; j++) g[i][j] = (f[i][j] - f[i][j - 1] + mod) % mod, printf("%d %d %d\n", i, j, f[i][j]);
	}
	for(int i = 0; i < N; i++) {
		int ans = 0;
		if(i & 1) {
			int j = (i - 1) / 2;
			for(int k = 1; k < N; k++) (ans += 1ll * g[k][j] * g[N - k][j] % mod * C[N - 1][k - 1] % mod) %= mod;
			cout << ans << " ";
		} else {
			int j = i / 2;
			ans = g[N][i];
			for(int k = 1; k < N; k++) ans = (ans - 1ll * g[k][j - 1] * f[N - k][j - 1] % mod * C[N][k] % mod + mod) % mod;
			cout << ans << " ";
		}
	}
}
/*
*/	

T3

首先与\(k\)无关,因为可以取到\(=\)

也就是说每个点只有选或不选两种状态

直接01分数规划,把每个\(a[i] - x\),问题转化为能不能删去一些点,使得剩下的权值\(>0\)

那么一个点能成为答案,当且仅当它不是孤立点且与其他不选的点形成了独立集(因为两条边之间最小有一个要选)

也就是说我们要找出\(a[i] - val\)最小的独立集,

但是似乎并不好搞,取一下负,找出\(val - a[i]\)最大的独立集

然后就可以dp了。

\(dp[i]\)表示考虑右侧的\(i\)个点,枚举上一个点\(j\),再枚举一下左边的点。观察能否加入独立集

第三维前缀和优化一下,时间复杂度:\(O(n^2)\)

换一种dp方式,加一个\(f[j]\)表示从\(j\)转移到当前点的最优代价

for i = 1...n
	dp[i] = max(f[j], 1 <= j < i)
	f[i] = dp[i]
	for all 区间 r = i
 	[l, r] = w
	f[0....l - 1]  += W

线段树优化一下,interesting。

复杂度:\(O(nlog^2n)\)

#include<bits/stdc++.h>
#define chmax(a, b) (a = (a < b ? b : a))
#define chmin(a, b) (a = (a < b ? a : b))
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
#define ls k << 1
#define rs k << 1 | 1 
using namespace std;
const int MAXN = 30001;
const double INF = 1e18, eps = 1e-9;
char buf[1 << 21], *p1 = buf, *p2 = buf;
inline int read() {
	char c = getchar(); int x = 0, f = 1;
	while(c < '0' || c > '9') {if(c == '-')f =- 1; c = getchar();}
	while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}
int N, M, K, L[MAXN], R[MAXN];
vector<int> v[MAXN];
double dp[MAXN], a[MAXN], b[MAXN], ta[MAXN], tb[MAXN];
struct Node {
	int l, r, siz;
	double v, f;
}T[MAXN << 2];
void add(int k, double val) {
	T[k].v += val; T[k].f += val;
}
void pushdown(int k) {
	if(T[k].f < eps) return ;
	add(ls, T[k].f); add(rs, T[k].f);
	T[k].f = 0;
}
void update(int k) {
	T[k].v = max(T[ls].v, T[rs].v);
}
void Build(int k, int ll, int rr) {
	T[k].l = ll; T[k].r = rr; T[k].siz = rr - ll + 1; T[k].v = T[k].f = 0;
	if(ll == rr) return ;
	int mid = T[k].l + T[k].r >> 1;
	Build(ls, ll, mid); Build(rs, mid + 1, rr);
	update(k);
}
void IntAdd(int k, int ll, int rr, double val) {
	if(ll <= T[k].l && T[k].r <= rr) {
		add(k, val); return ;
	}
	pushdown(k);
	int mid = T[k].l + T[k].r >> 1;
	if(ll <= mid) IntAdd(ls, ll, rr, val); 
	if(rr >  mid) IntAdd(rs, ll, rr, val);
	update(k);
}
double Query(int k, int ll, int rr) {
	if(ll <= T[k].l && T[k].r <= rr) return T[k].v;
	pushdown(k);
	int mid = T[k].l + T[k].r >> 1;
	if(ll > mid) return Query(rs, ll, rr);
	else if(rr <= mid) return Query(ls, ll, rr);
	else return max(Query(ls, ll, rr), Query(rs, ll, rr));
}
bool check(double val) {
	double sum = 0;
	for(int i = 1; i <= N; i++) ta[i] = max((double)0, val - a[i]), sum += a[i] - val;
	for(int i = 1; i <= M; i++) tb[i] = max((double)0, val - b[i]), sum += b[i] - val;
	Build(1, 0, M);
	for(int i = 1; i <= M + 1; i++) {
		dp[i] = Query(1, 0, i - 1) + tb[i];
		for(int j = 0; j < v[i].size(); j++)
			IntAdd(1, 0, L[v[i][j]] - 1, ta[v[i][j]]);
		if(i != M + 1) IntAdd(1, i, i, dp[i]);
	}
	double ans = 0;
	for(int i = 1; i <= M + 1; i++) ans = max(ans, dp[i]);
	return sum + ans > -eps;
}
main() {
	//freopen("a.in", "r", stdin);
	N = read(); M = read(); K = read();
	double l = INF, r = -INF;
	for(int i = 1; i <= N; i++) a[i] = read(), chmin(l, a[i]), chmax(r, a[i]);
	for(int i = 1; i <= M; i++) b[i] = read(), chmin(l, b[i]), chmax(r, b[i]);
	for(int i = 1; i <= N; i++) {
		L[i] = read(), R[i] = read();
		v[R[i]].push_back(i);
	}
	double ans = -1;
	while(r - l > eps) {
		double mid = (l + r) / 2;
	//	printf("%.10lf\n", mid);
		if(check(mid)) l = mid , ans = mid;
		else r = mid;
	}
	printf("%.10lf", ans);

}
posted @ 2018-10-04 22:15  自为风月马前卒  阅读(187)  评论(0编辑  收藏  举报

Contact with me