Summer Holiday Contest 2020 Day 2官方题解

D2T1

Description

b6e0所在的城市突然爆发了一种流感。城市里的人可以表示为一个 n n m m 列的矩阵,每个格子代表一个人。

一开始,其中某些人是感染的,某些人则没有感染。如果对于一个没有感染的人 ( i , j ) (i,j) ,与他相邻的人中有两个及以上的感染者,那么他一定会被传染;否则一定不会。

现在,请求出在经过多少轮传染后,所有人都没有感染。

“一轮传染”定义为:对于所有没有感染的人 ( i , j ) (i,j) ,如果它会被传染,则变为感染的状态。对于所有感染上的人,因为会被治好,所以变为没有感染的状态。

请注意传染的顺序:传染是发生在治好之前的,并且新被传染的人不会被治好。即先由感染者传染,再对原感染者染者消除感染,新感染者不会消除感染

Solution

直接模拟即可。

D2T2

Description

b6e0看见了一段楼梯。这段楼梯有 n n 阶,他现在在第 0 0 阶,他想要到第 n n 阶。

对于所有 i i ( 1 i n 1\le i\le n ),可以从第 a i a_i 阶~第 i 1 i-1 阶直接跨到第 i i 阶。每一阶还有一个值 b i b_i ,表示从 j j i i ( j < i j<i )花费 b j + b i b_j+b_i 的体力。特殊地, b 0 = 0 b_0=0

b6e0想知道他到达第 n n 阶花费的最少体力是多少。

Solution

从宏观上来讲,这是一道简单的 d p dp 题。

状态设计: d p i dp_i b 6 e 0 b6e0 1 1 号台阶爬到第 i i 号台阶所耗的体力的最小值。

状态转移: d p i = m i n a i j i ( d p j + b j ) + b i dp_i=min_{a_i≤j<i}{(dp_{j}+b_j)}+b_i

此时,若暴力转移时间复杂度会达到 O ( n 2 ) O(n^2) 。于是,我们考虑优化。


优化1: 单调队列(by b6e0)

可以发现,每次的区间的变化相当于窗口的滑动,于是我们可以使用单调队列。

优化2: 线段树(by ducati)

c i = d p i + b i c_i=dp_i+b_i 。对于每次转移,我们查询区间 [ a i , i 1 ] [a_i,i-1] 中各 c i c_i 的最小值,相当于线段树的区间查询。查询后,我们在第 i i 个位置加入 c i c_i (即在区间 [ i , i ] [i,i] 中各数均加 c i c_i ),相当于线段树的单点修改。

时间复杂度: O ( n l o g 2 n ) O(nlog_2n)

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[500010],b[500010],dp[500010];
vector<int>q;
signed main()
{
	int n,i,p;
	cin>>n;
	for(i=1;i<=n;i++)
		cin>>a[i];
	for(i=1;i<=n;i++)
		cin>>b[i];
	dp[1]=b[1];
	q.push_back(1);
	for(i=2;i<=n;i++)
	{
		p=*lower_bound(q.begin(),q.end(),a[i]);
		dp[i]=dp[p]+b[p]+b[i];
		while(q.size()&&dp[q[q.size()-1]]+b[q[q.size()-1]]>=dp[i]+b[i])
			q.pop_back();
		q.push_back(i);
	}
	cout<<dp[n];
	return 0;
}//by b6e0

D2T3

Description

给定拥有 n n 个数的序列 A A 。定义 f ( k ) f(k) 表示在序列 A A 中找出拥有 k k 个元素的子序列,并将它们的积求和。

例如,当 A = A= { 2 , 3 , 4 2,3,4 }, f ( 2 ) = 26 f(2)=26 。原因如下: 所有的长度为 2 2 的子序列共有3个,分别是{ 2 , 3 2,3 },{ 2 , 4 2,4 },{ 3 , 4 3,4 },它们的乘积之和为 2 × 3 + 2 × 4 + 3 × 4 = 26 2\times3+2\times4+3\times4=26

现在,凉心的Ducati会 q q 次询问你 l = x y r = l y i = l r f ( i ) \sum_{l=x}^y\sum_{r=l}^y\sum_{i=l}^{r}f(i) 的值。

由于答案可能过大,请将答案对 1000000007 ( 1 0 9 + 7 ) 1000000007(10^9+7) 取模。

Solution

Subtask 1-3

直接暴力即可。

Subtask 4-5

事实上这题的难度并不大,但是一些选手为什么会被几个Σ吓跑

我们考虑一下,这题的重点——
①找出 f ( 1 ) , f ( 2 ) , f ( n ) f(1),f(2),……f(n) ,显然要预处理。
②快速求得 l = x y r = l y i = l r f ( i ) \sum_{l=x}^y\sum_{r=l}^y\sum_{i=l}^{r}f(i)

首先,我们考虑②——即,假设①已经处理完了,我们怎么快速求出 l = x y r = l y i = l r f ( i ) \sum_{l=x}^y\sum_{r=l}^y\sum_{i=l}^{r}f(i) 呢?

一个套路——分别求贡献。对于第 i i 个位置,它对答案贡献的次数为 ( i x + 1 ) ( y i + 1 ) (i-x+1)(y-i+1) 次——即,左端点有 i x + 1 i-x+1 种,右端点有 y i + 1 y-i+1 种,将它们两两搭配形成的区间会包含第 i i 个位置分别一次。

所以, l = x y r = l y i = l r f ( i ) = Σ i = x y ( i x + 1 ) ( y i + 1 ) f ( i ) \sum_{l=x}^y\sum_{r=l}^y\sum_{i=l}^{r}f(i)=Σ_{i=x}^y (i-x+1)(y-i+1)f(i)

于是,下面的难点在于如何预处理出 f ( 1 ) , f ( 2 ) f ( n ) f(1),f(2)……f(n)


考虑 d p dp 。状态设计 d p i , j dp_{i,j} 表示,目前从 j j n n 看到的这些数中,任选 i i 个不同的数, d p i , j dp_{i,j} 即为所有这样组合的积之和。

如, A = 1 , 2 , 3 , 4 , 5 A=1,2,3,4,5 ,那么 d p 2 , 3 = 3 × 4 + 3 × 5 + 4 × 5 = 47 dp_{2,3}=3×4+3×5+4×5=47

于是,怎么状态转移呢?式子很简单:

d p i , j = d p i , j + 1 + a i × d p i 1 , j + 1 dp_{i,j}=dp_{i,j+1}+a_i×dp_{i-1,j+1}

它非常好理解,只需要举个例子就可以啦~


同样,设 A = 1 , 2 , 3 , 4 , 5 A=1,2,3,4,5 ,我们现在想要得到 d p 3 , 2 dp_{3,2}

首先,已经得到 d p 3 , 5 = d p 3 , 4 = 0 dp_{3,5}=dp_{3,4}=0 d p 3 , 3 = 3 × 4 × 5 = 60 dp_{3,3}=3×4×5=60

那么,
d p 3 , 2 dp_{3,2}
= 2 × 3 × 4 + 2 × 3 × 5 + 2 × 4 × 5 + 3 × 4 × 5 =2×3×4+2×3×5+2×4×5+3×4×5
= 2 × ( 3 × 4 + 3 × 5 + 4 × 5 ) + 3 × 4 × 5 =2×(3×4+3×5+4×5)+3×4×5
= d p 3 , 3 + a 2 × d p 2 , 4 =dp_{3,3}+a_2×dp_{2,4}

显然 f ( i ) = d p i , 1 f(i)=dp_{i,1} 。直接运用公式 l = x y r = l y i = l r f ( i ) = Σ i = x y ( i x + 1 ) ( y i + 1 ) f ( i ) \sum_{l=x}^y\sum_{r=l}^y\sum_{i=l}^{r}f(i)=Σ_{i=x}^y (i-x+1)(y-i+1)f(i) 即可。

估一下时间复杂度:
①预处理出 f i ( 1 i n ) f_i(1≤i≤n) ,代价为 O ( n 2 ) O(n^2)
②每次询问中,求 Σ i = x y ( i x + 1 ) ( y i + 1 ) f ( i ) Σ_{i=x}^y (i-x+1)(y-i+1)f(i) 的值,代价为 O ( q n ) O(qn)

所以,总时间复杂度为 O ( n 2 + q n ) O(n^2+qn) ,可以通过。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
 
int n,q,cnt=0;
int tmp[1005],a[1005],dp[1005][1005],b[1005];

signed main()
{
	cin>>n>>q;
	for (int i=1;i<=n;i++)  cin>>tmp[i];
	
	sort(tmp+1,tmp+n+1);
	
	for (int i=1;i<=n;i++)
	{
		if (tmp[i]!=tmp[i-1])  a[++cnt]=tmp[i];
	}
	for (int i=cnt;i>=1;i--)  dp[1][i]=(dp[1][i+1]+a[i])%mod;
	for (int i=2;i<=cnt;i++)
	{
		for (int j=cnt;j>=1;j--)  dp[i][j]=(dp[i][j+1]+(a[j]*dp[i-1][j+1])%mod)%mod;
	}
	for (int i=1;i<=cnt;i++)  b[i]=dp[i][1]%mod;
	while(q--)
	{
		int l,r,tot=0;
		cin>>l>>r;
		
		for (int i=l;i<=r;i++)  tot=(tot+(((i-l+1)*(r-i+1))%mod*b[i])%mod)%mod;
		cout<<tot%mod<<endl;
	}
	return 0;
}//submitted by b6e0, writen by ducati

D2T4(by 关怀)

Descripition

求在 n n 的倍数中,一个数最多能有多少个二进制 1 1

Solution

Subtask 1

显然,我们只需要暴力枚举从 1 1 2 n 2^n 。每次判断它是否为 n n 的倍数,并求出其二进制 1 1 的数量即可。

时间复杂度: O ( 2 n ) O(2^n)

Subtask 2-3

考虑图论建模。

显然,从 k k 2 k 2k 中,在其二进制表示下末尾多了一个 0 0 ,没有多 1 1 。故我们可以从 k k 2 k 2k 连一条权值为0的边。

同时,从 k k 2 k + 1 2k+1 中,在其二进制表示下末尾多了一个 1 1 。故我们可以从 k k 2 k + 1 2k+1 连一条权值为1的边。

为了方便,我们需要把所有节点的编号 n n 取模。此时答案即为 1 1 0 0 的最短路径。

用双端队列快速求出其最短路径即可。

时间复杂度: O ( n ) O(n)

Code

#include <bits/stdc++.h>
using namespace std;
const int INF = 1000;
const int offset = 4e6 + 7;
int o[offset * 2 + 1];
int f[offset];
bool vis[offset];
int n,T;

int main()
{
   	scanf("%d",&T);
    int st=0;
    while (T--) {
        scanf("%d", &n);
        memset(o, 0, sizeof(o));
        memset(vis, 0, sizeof(vis));
        memset(f, 0, sizeof(f));
        for (int i = 0; i < n; i++) f[i] = INF;
        int h = offset, t = offset;
        o[h] = 1;
        f[1] = 1;
        while (h <= t) {
            int u = o[h++];
            if (f[u] < f[2 * u % n]) {
                f[ 2 * u % n ] = f[u];
                o[--h] = 2 * u % n;
            }
            if (f[u] + 1 < f[(2 * u + 1) % n]) {
                f[ (2 * u + 1) % n ] = f[u] + 1;
                o[++t] = (2 * u + 1) % n;
            }
        }
        printf("%d\n", f[0]);
    }
    return 0;
}//by b6e0

撒花✿✿ヽ(°▽°)ノ✿撒花

猜你喜欢

转载自blog.csdn.net/Cherrt/article/details/106582752