2019杭电多校第十场

这次终于不是苟上的首页了

1003. Valentine's Day

solved at 01:02

\(n\)个物品,每个物品有\(p_i\)的概率使你快乐,每个物品是否让你快乐是独立事件,你要选择一个子集,使得你快乐次数恰好为一的概率最大 我并不是一个数学选手,读完之后就交给zggg了,然而zggg一直没搞出来,我突然想起来这题我好像去年做过?!一翻vjudge果然如此,改了一下读入就过了...

就是选择的一定是\(p\)最大的若干个(选了的物品的p值一定大于等于没选的),证明可以去看codeforces442B的官方题解

1005. Welcome Party

solved at 01:24(+1)

队友翻译后的题意:有n个物品\((2<=n<=1e5)\),每个物品有两个权值\(a_i, b_i\), 你要将这些物品划分成两个非空集合A, B,使得A中物品的最大a值与B中物品的最大b值的差值的绝对值最小

首先按a排序,然后从大到小枚举\(a_i\)作为A中的最大值,那么a值更大的一定在集合B里,只要然后lower_bound在尚未枚举到的物品(a值小于等于当前枚举的物品)里找第一个大于等于当前枚举的a值的b值,然后再找它的前一个(最后一个小于当前枚举a值的),然后和已经枚举过了的物品的最大b值比较一下看哪些值可以作为最大b值更新答案即可,可以用multiset维护

队友写的不放代码了

1008. Coins

sovled at 03:08(+2)

\(n(1<=n<=1e5)\)组物品,每组物品有两个,价值分别为\(a_i, b_i\),你要选择k个物品最大化物品价值,限制条件是如果你选了某一组的b,那你必须选这一组的a,输出\(2n\)个数,代表\(k\)\(1\)\(2n\)的答案

队友给了一个奇诡的贪心方案,对于一组物品,如果a大于等于b,则把a,b视为两个物品,否则将ab合并视为1个size为2权值为平均值的物品,将这若干个物品排序,然后每个\(k\)单独处理

对于一个\(k\),直接看后k个物品,即选择\([2n-k+1, 2n]\)区间内的物品,如果说这个区间是合法的,那么区间物品的值加起来就是答案

如果这个区间不合法,即\(2n-k\)\(2n-k+1\)是一个size为2的物品,那么有两种情况,一种是选择这个size为2的物品,那么就要舍弃一个已选的物品,舍弃最小的一个就好了,可以舍弃的是所有独立物品和合并物品的b(不会导致不合法情况,因为你如果舍弃的是一个独立a那么它的对应b必然没有被选(否则因为它们是独立的,b肯定比a小,那么a不可能是最小的)),第二种是不选择这个物品,那么没选的里面找一个最大的,可以找所有的独立物品以及合并物品的a(同理也不会导致非法情况),要注意当前这个合并物品的a也可以取

虽然不是我写的但还是放出来给大家感受一下这神奇的贪心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<bits/stdc++.h>

using namespace std;
const int maxn=1e5+5;
int n;
long long a[maxn],b[maxn];

struct ele{
int id;
long long val,sz;
bool operator<(const ele& oth) const{
return val<oth.val;
}
};
vector <ele> arr;
vector <long long> mmax;
vector <long long> ans;
int main(void){
int T;
scanf("%d", &T);
while(T--){
scanf("%d", &n);
arr.clear();ans.clear();mmax.clear();
for (int i=1;i<=n;i++){
scanf("%lld%lld",&a[i],&b[i]);
a[i]*=2;b[i]*=2;
if (a[i]>=b[i]) arr.push_back({i,a[i],1}),arr.push_back({i,b[i],1});
else arr.push_back({i,(a[i]+b[i])/2,2});

}
sort(arr.begin(),arr.end());
for (int i=0;i<arr.size();i++){
long long tmp;
if (arr[i].sz==1)tmp=arr[i].val;
else tmp=a[arr[i].id];
if (mmax.size()==0) mmax.push_back(tmp);
else mmax.push_back(max(mmax[mmax.size()-1],tmp));
}
long long mmin=1e18;
long long sum=0;
for (int i=arr.size()-1;i>=0;i--){
if(arr[i].sz==1){
sum+=arr[i].val;
ans.push_back(sum);
mmin=min(mmin,arr[i].val);
}else{
long long tmp=0;
tmp=sum-mmin+arr[i].val*2;
tmp=max(tmp,sum+mmax[i]);
ans.push_back(tmp);
sum+=arr[i].val*2;ans.push_back(sum);
mmin=min(mmin,b[arr[i].id]);
}
}
for (int i=0;i<ans.size();i++) printf("%lld%c",ans[i]/2," \n"[i==ans.size()-1]);
}
}

1009. Block Breaker

solved at 00:37

签到题,bfs即可

1011. Make Rounddog Happy

solved at 04:20(+1)

一个长为\(n(1<=n<=3e5)\)的数列,定义一个区间是快乐的当且仅当这个区间的元素两两不同并且区间最大值减去区间长度小于等于\(k\)(一个给定值), 求快乐的区间个数

一开始没看到元素互异,那就是个单调栈裸题...

对于位置\(i\)上的数\(a_i\), 处理出四个数,以\(a_i\)为最大值的最左位置\(l_i\), 最右位置\(r_i\)(这两个数组用单调栈线性求出), 以\(a_i\)为左端点,能保证元素互异的最右位置\(isl_i\),以及为右端点的最左位置\(isr_i\)(这两个数组用双指针线性求出)

然后你要写这样一个函数calc(len, L, R)即某个给定的数左边有L个可以选,右边有R个(均不包括本身), 必须选这个给定的数,要求区间长度大于等于len的方案数,这个函数一次调用应当是常数时间的(这个函数是我队友写的)

考虑每个位置\(i\),它作为最大值能产生贡献的区间是\([\max(l_i, isr_i), \min(r_i, isl_i)]\), 以1 2 3 6 5 1 2 3为例, 当前到是位置4(即数字6), 枚举一边的端点计算贡献,以枚举左端点为例,左端点为1时能产生贡献的右端点是5,即把(1, 2, 3, 6, 5)能产生的贡献加到答案里去,然后考虑左端点为2,把(2,3,6,5,1)产生的贡献加到答案里,此时(2,3,6,5)的贡献被重复计算了,需要减掉,依次类推,最后以4位置的6为最大值的贡献就是\(f(1,2,3,6,5)+f(2,3,6,5,1)-f(2,3,6,5)+f(3,6,5,1,2)-f(3,6,5,1)+f(6,5,1,2,3)-f(6,5,1,2)\)

如果你每次枚举的都是左端点,那么肯定会TLE, 应当枚举左右两边较短的那一边,复杂度并不会分析,随机数据3e5大概总和是1.2e6次,我猜总复杂度是\(nlogn\)

当然也可以用分治做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <bits/stdc++.h>
using namespace std;

const int N = 3e5 + 10;

int T, a[N], n, k, l[N], r[N], isl[N], isr[N];
int stk[N], top;
int cnt[N];
long long ans;

long long calc(long long v, long long L, long long R) {
if(v <= 1)
return (L + 1) * (R + 1);
if(L + R + 1 < v) return 0;
v--;
long long l1 = max(v - R, 0LL), l2 = L;
long long l3 = min(v, l2);
return (l3 + l1) * (l3 - l1 + 1) / 2 + (l3 - l1 + 1) * (R - v + 1) + max(0LL, l2 - v) * (R + 1);
}

int main() {
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &k);
ans = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]); cnt[a[i]] = 0;
}
top = 0;
a[0] = a[n + 1] = 1e9;
stk[top++] = 0;
for(int i = 1, j = 0; i <= n; ++i) {
while(a[stk[top - 1]] < a[i]) top--;
l[i] = stk[top - 1] + 1;
stk[top++] = i;
while(j < n && cnt[a[j + 1]] == 0) cnt[a[++j]]++;
isl[i] = j;
cnt[a[i]]--;
}
for(int i = 1; i <= n; ++i) cnt[a[i]] = 0;
top = 0;
stk[top++] = n + 1;
for(int i = n, j = n + 1; i >= 1; --i) {
while(a[stk[top - 1]] < a[i]) top--;
r[i] = stk[top - 1] - 1;
stk[top++] = i;
while(j > 1 && cnt[a[j - 1]] == 0) cnt[a[--j]]++;
isr[i] = j;
cnt[a[i]]--;
}
for(int i = 1; i <= n; ++i) {
int len = a[i] - k;
l[i] = max(l[i], isr[i]);
r[i] = min(r[i], isl[i]);
if(l[i] + r[i] > i * 2) {
int le = l[i], ri = min(r[i], isl[le]), pre = ri;
ans += calc(len, i - le, ri - i);
for(le++; le <= i; ++le) {
ri = min(r[i], isl[le]);
ans += calc(len, i - le, ri - i);
ans -= calc(len, i - le, pre - i);
pre = ri;
}
}
else {
int ri = r[i], le = max(l[i], isr[ri]), pre = le;
ans += calc(len, i - le, ri - i);
for(ri--; ri >= i; --ri) {
le = max(l[i], isr[ri]);
ans += calc(len, i - le, ri - i);
ans -= calc(len, i - pre, ri - i);
pre = le;
}
}
}
printf("%lld\n", ans);
}
return 0;
}

多校打完了,是时候整理一波模板了,和去年一样都是灰名并没有什么区别,明显感受到这次多校曲线总体趋势是上升的,那么下半年加油吧