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\),那么答案为
这个应该比较好理解,就是左右分别算一算。
- 如果直径是\(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);
}