Catalog
  1. 1. Day5
    1. 1.0.1. 最小差异矩阵(a.cpp, a.in, a.out)
      1. 1.0.1.1. 题面
      2. 1.0.1.2. 考场思路
      3. 1.0.1.3. 考场代码
      4. 1.0.1.4. 题解
      5. 1.0.1.5. std
    2. 1.0.2. 分割序列(b.cpp, b.in, b.out)
      1. 1.0.2.1. 考场思路
      2. 1.0.2.2. 考场代码
      3. 1.0.2.3. 题解
      4. 1.0.2.4. std
    3. 1.0.3. 树的魔法值(C.cpp, C.in, C.out)
      1. 1.0.3.1. 考场思路
      2. 1.0.3.2. 考场代码
      3. 1.0.3.3. 题解
      4. 1.0.3.4. std
解题报告-2019国庆清北Day5

https://download.csdn.net/download/qq_45721135/11866519

Day5

最小差异矩阵(a.cpp, a.in, a.out)

题面

【题目描述】

有一个 n*m 的矩阵,矩阵的每个位置上可以放置一个数。对于第 i 行,第 i 行的差异定

义为该行的最大数和最小数的差。一个矩阵的差异,定义为矩阵中每一行差异的最大值。现

在给定 k 个数 v[1..k],问:从这 k 个数中选 n*m 个数放入矩阵,能够得到的矩阵的差异最

小值是多少。

【输入格式】

第一行三个整数,k, n, m,表示有 k 个数可选,矩阵的行数和列数分别为 n 和 m。

第二行 k 个整数,表示备选的数 v[1..k]。

【输出格式】

输出一个数,表示能够得到的最小差异值

【样例输入】

5 2 2

7 5 8 2 3

【样例输出】

1

【数据范围】

对于 30%的数据,k<= 10, n <= 3, m <= 3

对于 100%的数据,n * m <= k <= 100000, n, m <= 1000,0<= v[i] <= 10^9

【时空限制】

256MB,1s

考场思路

仔细读题,就发现这道题目是要让最大值最小,于是貌似就可以去二分答案。可以发现,如果把所有可以填的数排一下序,那么最优方法中每一行所选的数在排好序的序列中一定是连续的,所以可以贪心去取每一行的数,来验证答案。

考场代码

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
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
#define INF 0x7fffffff
#define re register
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define qwq printf("qwq\n");

using namespace std;

int read()
{
register int x = 0,f = 1;register char ch;
ch = getchar();
while(ch > '9' || ch < '0'){if(ch == '-') f = -f;ch = getchar();}
while(ch <= '9' && ch >= '0'){x = x * 10 + ch - 48;ch = getchar();}
return x * f;
}

int n,m,k,l,r,mid,ans,a[100005];

int cmp(int x,int y){return x < y;}

int check(int v)
{
int p1 = 0,p2 = 0,cnt = 1;
while(cnt <= n)
{
p1 = p2 = p2 + 1;
while(p2 - p1 + 1 < m)
{
p2++;
if(p2 > k) return 0;
while(a[p2] - a[p1] > v) p1++;
}
cnt++;
}
return 1;
}

int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
k = read(); n = read(); m = read();
for(int i = 1; i <= k; i++) a[i] = read(),r = max(r,a[i]);
sort(a + 1, a + k + 1,cmp);
while(l < r)
{
mid = (l + r) >> 1
if(check(mid)) r = mid,ans = mid;
else l = mid + 1;
}
printf("%d\n",ans);
fclose(stdin); fclose(stdout);
return 0;
}

题解

在这里插入图片描述

std

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
#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 7;
int n, m, k, v[maxn];

int judge(int d)
{
int tmp = 0;
for (int i=1; i+m-1<=k; ++i)
{
if (v[i+m-1] - v[i] <=d)
++tmp, i += m - 1;
}
if (tmp >= n) return 1;
return 0;
}

int main()
{
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
scanf("%d%d%d", &k, &n, &m);
for (int i=1; i<=k; ++i)
scanf("%d", &v[i]);
sort(v + 1, v + k + 1);
int left = 0, right = 1e9;
while (left < right)
{
int mid = (left + right) / 2;
if (judge(mid))
right = mid;
else
left = mid + 1;
}
printf("%d\n", left);
return 0;
}

分割序列(b.cpp, b.in, b.out)

【题目描述】

给定一个长度为 n 的序列 v[1..n],现在要将这个序列分成 k 段(每段都丌能为空),定

义每一段的权值为该段上的所有数的戒和。定义整个序列的权值为每段权值的和。问:这个

序列的最大权值为多少。

【输入格式】

第一行两个数 n 和 k,意义如题意所示。

第二行 n 个数,表示这个序列 v[1..n]。

【输出格式】

输出一个数,代表这个序列的最大权值。

【输入样例】

5 2

7 5 8 2 3

【输出样例】

22

【数据范围】

对于 30%的数据,n<= 10, k <= 10

对于 60%的数据,n <= 100, k <= 100

对于 100%的数据,k <= n <= 2000,1<= v[i] <= 5 * 10^5

【时空限制】

256MB,1s

考场思路

一看到题就感觉像是个DP,于是就敲了一个$O(n^3)$的暴力。

状态设计:用$f[i][j]$表示把区间1~i分成j段能获得的最大价值。
转移:$f[i][j]=\max(f[k][j-1]+l[k+1][i]) $,其中$l[i][j]$表示对区间[i,j]按位或的结果。

考场代码

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
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
#define INF 0x7fffffff
#define re register
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define qwq printf("qwq\n");

using namespace std;

int read()
{
register int x = 0,f = 1;register char ch;
ch = getchar();
while(ch > '9' || ch < '0'){if(ch == '-') f = -f;ch = getchar();}
while(ch <= '9' && ch >= '0'){x = x * 10 + ch - 48;ch = getchar();}
return x * f;
}

long long n,k,v[100005],l[2005][2005],f[2005][2005];

int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
n = read(); k = read();
for(int i = 1; i <= n; i++) l[i][i] = read();
for(int i = 1; i <= n; i++)
for(int j = i + 1; j <= n; j++)
l[i][j] = l[i][j - 1] | l[j][j];
for(int i = 1; i <= n; i++) f[i][1] = l[1][i];
for(int i = 1; i <= n; i++)
for(int j = 2; j <= k; j++)
{
if(j > i) break;
for(int m = 1; m < i; m++)
f[i][j] = max(f[i][j],f[m][j - 1] + l[m + 1][i]);
}
printf("%lld\n",f[n][k]);
fclose(stdin); fclose(stdout);
return 0;
}

题解

在这里插入图片描述

考虑用单调性去优化dp的复杂度。

$l[i][j]$j不变,关于i单调递减

std

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
#include <bits/stdc++.h>
using namespace std;

const long long inf = 1ull << 30 << 20;
const int maxn = 2005;
vector<int> point[maxn];
int n, K, v[maxn], sum[maxn][maxn];
long long f[maxn][maxn];
int main()
{
freopen("b.in", "r", stdin);
freopen("b.out", "w", stdout);
cin >> n >> K;
for (int i=1; i<=n; ++i) cin >> v[i];
for (int i=1; i<=n; ++i) sum[i][i] = v[i];
for (int i=1; i<n; ++i)
for (int j=i+1; j<=n; ++j)
sum[i][j] = sum[i][j-1] | v[j];
for (int i=1; i<=n; ++i)
{
point[i].push_back(i);
for (int j=i-1; j>=1; --j)
if (sum[j][i] != sum[j+1][i])
point[i].push_back(j);
}
for (int k=1; k<=K; ++k)
for (int i=1; i<=n; ++i)
{
if (i < k) continue;
for (int j=0; j<point[i].size(); ++j)
{
int x = point[i][j];
if (x >= k)
f[i][k] = max(f[i][k], f[x-1][k-1] + sum[x][i]);
}
}
cout << f[n][K] << endl;
return 0;
}

树的魔法值(C.cpp, C.in, C.out)

【题目描述】

有一棵 k+1 层的满二叉树,那么该树有 2^k 个叶子节点。给定 n 个机器人(n=2^k),

编号从 1—n,编号为 i 的机器人的权值为 v[i]。我们现在要将这 n 个机器人分别放置在这 n

个叶子节点上,每个叶子节点放且只能放一个机器人。叶子节点的权值为该叶子节点上的机

器人的权值。非叶子节点的权值定义为该树中编号最大的机器人的权值。每个非叶子节点除

了权值以外,还有一个魔法值,该点的魔法值为其左右儿子节点的权值的乘积。整棵树的魔

法值定义为非叶子节点的魔法值的和。

问:将这 n 个机器人随机地放在这 n 个叶子节点上,树的魔法值的期望为多少。

【输入格式】

第一行为一个整数 k,含义如题所示。

第二行为 2^k 个整数,依次表示这 n 个机器人的权值。

【输出格式】

假设答案为一个丌可约分数 P/Q,则输出在模 1e9+7 意义下的 P * (Q^-1)模 1e9+7

的值。

【样例输入 1】

2

1 3 5 7

【样例输出 1】

59

【样例解释】

对于 n=4 的情况,机器人共有 24 种丌同的安放方案。其中,本质丌同的有 3 种,分

别 是 ((1,3),(5,7)), ((1,5),(3,7)), ((1,7),(5,3)) , 魔 法 值 分 别 为 13+57+3*7=59,

15+37+57=61, 17+53+57=57, 答案为(57+59+61)/3 = 59。

【样例输入 2】

2

1 5 3 7

【样例输出 2】

333333390

【数据范围】

30%的数据,k <= 3

60%的数据,k<= 10

100%的数据,k<= 18

【时空限制】

256MB,1s

考场思路

到这个题我只有半个小时了,看不懂样例2(老师讲题时才发现读错题了),想打一个暴力然后发现自己忘了怎么算逆元了,然后就gugu了。大概就按照线段树建树的方式瞎搞了一下,貌似还搞错了……

考场代码

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
#define INF 0x7fffffff
#define re register
#define mod 1000000007
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define qwq printf("qwq\n");

using namespace std;

int read()
{
register int x = 0,f = 1;register char ch;
ch = getchar();
while(ch > '9' || ch < '0'){if(ch == '-') f = -f;ch = getchar();}
while(ch <= '9' && ch >= '0'){x = x * 10 + ch - 48;ch = getchar();}
return x * f;
}

long long n,k,ans,cnt,sum,a[100005],pd[100005],yx[100005];

char s[25][40320],now[25];

long long ksm(long long x,long long y)
{
long long ans = 1;
while(y > 0)
{
if(y & 1) ans = (ans * x) % mod;
x = (x * x) % mod;
y = y >> 1;
}
return ans % mod;
}

int check()
{
for(int i = 1; i <= cnt; i++) if(strcmp(now,s[i]) == 0) return 0;
strcpy(s[++cnt],now);
return 1;
}

long long build(int l,int r)
{
if(l == r) return a[l];
int mid = (l + r) >> 1;
int t1 = build(l,mid);
int t2 = build(mid + 1,r);
sum += t1 * t2;
return max(t1,t2);
}

void tj()
{
if(check() == 0) return ;
sum = 0;
build(1,n);
ans = ans + sum;
}

void dfs(int k)
{
if(k > n)
{
tj();
return ;
}
for(int i = 1; i <= n; i++)
{
if(pd[i]) continue;
pd[i] = 1;
now[k - 1] = a[i];
yx[k] = a[i];
dfs(k + 1);
pd[i] = 0;
}
}

int main()
{
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
k = read();
n = 1 << k;
for(int i = 1; i <= n; i++) a[i] = read();
dfs(1);
if(ans % cnt == 0) printf("%lld\n",ans / cnt);
else printf("%lld\n",(ans * ksm(cnt,1000000005)) % (1000000007));
fclose(stdin); fclose(stdout);
return 0;
}

题解

  1. 30分:全排列,暴力求期望
  2. 60分:

在这里插入图片描述
在这里插入图片描述

  1. 100分

    听不懂……

std

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
#include <bits/stdc++.h>
using namespace std;

const int mod = 1e9 + 7;
const int maxn = (1 << 18) + 7;
typedef long long LL;

LL fac[maxn], inv_fac[maxn], bit[maxn], sum[maxn];
int n, v[maxn], k;
int powmod(int x, int times)
{
LL tmp = 1;
while (times > 0)
{
if (times & 1) tmp = tmp * x % mod;
x = (LL)x * x % mod;
times >>= 1;
}
return tmp;
}

LL C(int x, int y)
{
if (x < y) return 0;
return fac[x] * inv_fac[y] % mod * inv_fac[x-y] % mod;
}

int main()
{
freopen("c.in", "r", stdin);
freopen("c.out", "w", stdout);
fac[0] = 1; for (int i=1; i<maxn; ++i) fac[i] = fac[i-1] * i % mod;
inv_fac[maxn-1] = powmod(fac[maxn-1], mod - 2);
for (int i=maxn-2; i>=0; --i) inv_fac[i] = inv_fac[i+1] * (i + 1) % mod;
bit[0] = 1; for (int i=1; i<=20; ++i) bit[i] = bit[i-1] * 2;

scanf("%d", &k);
n = bit[k];
for (int i=1; i<=bit[k]; ++i) scanf("%d", &v[i]);
LL ans = 0;
for (int d=1; d<=k; ++d)
{
LL tmp = 0;
for (int i=n; i>=bit[d]; --i)
sum[i] = (sum[i+1] + C(i-1-bit[d-1], bit[d-1]-1) * v[i]) % mod;
for (int j=n-1; j>=bit[d-1]; --j)
tmp = (tmp + C(j-1, bit[d-1]-1) * v[j] % mod * sum[max(bit[d], (LL)j + 1)]) % mod;
ans = (ans + tmp * fac[bit[d-1]] % mod * fac[bit[d-1]] % mod * fac[n-bit[d]] % mod
* 2 % mod * bit[k-d]) % mod;
}
ans = ans * inv_fac[n] % mod;
cout << ans << endl;
return 0;
}
anf("%d", &k);
n = bit[k];
for (int i=1; i<=bit[k]; ++i) scanf("%d", &v[i]);
LL ans = 0;
for (int d=1; d<=k; ++d)
{
LL tmp = 0;
for (int i=n; i>=bit[d]; --i)
sum[i] = (sum[i+1] + C(i-1-bit[d-1], bit[d-1]-1) * v[i]) % mod;
for (int j=n-1; j>=bit[d-1]; --j)
tmp = (tmp + C(j-1, bit[d-1]-1) * v[j] % mod * sum[max(bit[d], (LL)j + 1)]) % mod;
ans = (ans + tmp * fac[bit[d-1]] % mod * fac[bit[d-1]] % mod * fac[n-bit[d]] % mod
* 2 % mod * bit[k-d]) % mod;
}
ans = ans * inv_fac[n] % mod;
cout << ans << endl;
return 0;
}
Author: wflight
Link: http://yoursite.com/2019/10/19/解题报告-2019国庆清北Day5/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付寶

Comment