解題報告-2019國慶清北Day5

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

考場思路

仔細讀題,就發現這道題目是要讓最大值最小,於是貌似就可以去二分答案。可以發現,如果把所有可以填的數排一下序,那麼最優方法中每一行所選的數在排好序的序列中一定是連續的,所以可以貪心去取每一行的數,來驗證答案。

考場代碼

#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

#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(n3)O(n^3)的暴力。

狀態設計:用f[i][j]f[i][j]表示把區間1~i分成j段能獲得的最大價值。
轉移:$f[i][j]=\max(f[k][j-1]+l[k+1][i]) ,其中l[i][j]$表示對區間[i,j]按位或的結果。

考場代碼

#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]l[i][j]j不變,關於i單調遞減

std

#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了。大概就按照線段樹建樹的方式瞎搞了一下,貌似還搞錯了……

考場代碼

#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

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章