1027上午考试总结
1027上午考试总结
T1
题目大意:
有\(n\)个位置,可以往里面填1到n的数.有\(m\)个限制条件,形如"[l,r]",表示从l到r放的数都不一样,问如何填数使整个序列字典序最小.\(n,m <= 1e5\)
贪心,构造.
首先将区间按左端点排序,某一些区间假设被别的区间完全包含的话,那么他肯定是没有用的,只需要考虑那些大的区间.然后每次往区间里填数都是从左往右1,2,3...这么填,可以保证字典序最小.假如两段区间有重合部分,优先把前一段区间填完后,在"回收"一些数填到下一个区间里,这个过程可以用优先队列搞一搞,我用的是时间戳,可能会被卡把.用了优先队列的话时间复杂度就是\(O(n log n)\).
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e5 + 5;
int t, n, m;
int ans[N], vis[N];
struct line { int l, r; } a[N];
int cmp(line a, line b) {
if(a.l == b.l) return a.r > b.r;
return a.l < b.l;
}
int main() {
for(t = read(); t ; t --) {
n = read(); m = read();
for(int i = 1;i <= n; i++) ans[i] = 1, vis[i] = 0;
for(int i = 1;i <= m; i++) a[i].l = read(), a[i].r = read();
sort(a + 1, a + m + 1, cmp);
int now = 0;
for(int i = 1;i <= m; i++) {
if(now >= a[i].r) continue;
int tmp = 1;
if(a[i].l >= now + 1) for(int j = a[i].l;j <= a[i].r; j++) ans[j] = tmp ++;
else {
for(int j = a[i].l;j <= now; j++) vis[ans[j]] = i;
for(int j = now + 1;j <= a[i].r; j++) {
while(vis[tmp] == i) tmp ++; ans[j] = tmp; vis[tmp] = i;
}
}
now = a[i].r;
}
for(int i = 1;i <= n; i++) printf("%d%c", ans[i], i == n ? '\n' : ' ');
}
return 0;
}
T2
题目大意:
有一辆货车,载货量为\(m\),有\(n\)个人,每个人有\(a[i]\)重量的物品,现在已经给定这\(n\)个人的顺序,问对于每个人,在强制放他的物品后,他前面最多有多少人可以放进自己的物品,输出剩下的最少的人数.\(n <= 1e5, a <= 1e9\)
离散化 + 线段树.
当强制放了第\(i\)个人后,货车的剩余容量就是\(m - a[i]\),现在要找出\(i\)前面最多能有几个人放进去,肯定是优先放物品重量小的.我们先把所有物品离散化,然后开一颗权值线段树,维护物品的个数和物品的总重量,然后在权值线段树里找个数就好了.当第\(i\)个人强制放也放不进去时记得特判输出0就好了.
#include <bits/stdc++.h>
#define ls(o) (o << 1)
#define rs(o) (o << 1 | 1)
#define mid ((l + r) >> 1)
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48; isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e5 + 5;
int T, n, m;
int a[N], b[N];
struct tree { int num; long long sum; } t[N << 2];
void build(int o, int l, int r) {
t[o].num = t[o].sum = 0;
if(l == r) return ;
build(ls(o), l, mid); build(rs(o), mid + 1, r);
}
int query(int o, int l, int r, int res) {
if(res == 0) return 0;
if(l == r) {
if(t[o].sum <= res) return t[o].num;
else {
int num = res / b[l]; return min(num, t[o].num);
}
}
if(t[o].sum <= res) return t[o].num;
if(t[ls(o)].sum <= res) return t[ls(o)].num + query(rs(o), mid + 1, r, res - t[ls(o)].sum);
else return query(ls(o), l, mid, res);
}
void insert(int o, int l, int r, int x, int val) {
if(l == r) { t[o].num ++; t[o].sum += val; return ; }
if(x <= mid) insert(ls(o), l, mid, x, val);
if(x > mid) insert(rs(o), mid + 1, r, x, val);
t[o].num = t[ls(o)].num + t[rs(o)].num;
t[o].sum = t[ls(o)].sum + t[rs(o)].sum;
}
int main() {
for(T = read(); T ; T --) {
n = read(); m = read();
build(1, 1, n);
for(int i = 1;i <= n; i++) a[i] = b[i] = read();
sort(b + 1, b + n + 1);
int cnt = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1;i <= n; i++) {
int tmp = m - a[i], li = lower_bound(b + 1, b + n + 1, a[i]) - b;
if(tmp < 0) { printf("%d ", i); }
else {
int num = query(1, 1, n, tmp);
printf("%d ", i - num - 1);
insert(1, 1, n, li, a[i]);
}
}
printf("\n");
}
return 0;
}
T3
题目大意:
给定\(n\)个数(有正有负),可以任取前几个把前几个数分成\(k\)段,使最大的那一段的总和最小,输出这个最小值.\(n <= 1e5\).
二分 + DP + 离散化 + 树状数组
二分的话当然是二分那个最大值, 设为\(mid\),然后跑一边DP,\(f[i]\)表示前\(i\)个数能分成的小于等于\(mid\)的段数最多有几个,那么DP转移方程就是:
\(f[i] = max(f[j] + 1) (sum[i] - sum[j] <= mid)\).\(sum\)表示前缀和.
但是有一点要注意:当\(max(f[j]) = 0\)时,要判断\(sum[i]\)是否小于等于\(mid\),如果不是,那么\(f[i]\)还等于0.
我们发现这个式子是\(O(N ^ 2)\)的,考虑用树状数组优化一下,怎么优化呢?
我们要找的\(f[j]\)一定是满足这个条件的\(sum[j] >= sum[i] - mid\),我们开一个权值树状数组,\(t[x]\)维护比\(x\)大的最大的\(f\)值, 离散化之后查询和插入就好了.
总复杂度\(O(n log^2n)\).
#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e5 + 5;
const long long inf = 1e18;
int n, k, cnt;
long long ans;
int a[N], f[N], t[N];
long long b[N], sum[N];
int lowbit(int x) { return x & (-x); }
void change(int x, int val) { for(; x ; x -= lowbit(x)) t[x] = max(t[x], val); }
int query(int x) { int res = 0; for(; x < N; x += lowbit(x)) res = max(res, t[x]); return res; }
int judge(long long Mid) {
for(int i = 1;i < N; i++) t[i] = 0;
for(int i = 1;i <= n; i++) {
int li = lower_bound(b + 1, b + cnt + 1, sum[i] - Mid) - b;
int ll = lower_bound(b + 1, b + cnt + 1, sum[i]) - b;
int tmp = query(li) + 1;
if(tmp == 1 && sum[i] > Mid) f[i] = 0;
else f[i] = tmp;
change(ll, f[i]);
if(f[i] >= k) return 1;
}
return 0;
}
int main() {
for(int T = read(); T ; T --) {
n = read(); k = read();
for(int i = 1;i <= n; i++) a[i] = read(), b[i] = sum[i] = sum[i - 1] + a[i];
sort(b + 1, b + n + 1);
cnt = unique(b + 1, b + n + 1) - b - 1;
long long l = -1e10, r = 1e10;
while(l <= r) {
if(judge(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%lld\n", ans);
}
return 0;
}