D2T1
Description
b6e0所在的城市突然爆发了一种流感。城市里的人可以表示为一个 行 列的矩阵,每个格子代表一个人。
一开始,其中某些人是感染的,某些人则没有感染。如果对于一个没有感染的人 ,与他相邻的人中有两个及以上的感染者,那么他一定会被传染;否则一定不会。
现在,请求出在经过多少轮传染后,所有人都没有感染。
“一轮传染”定义为:对于所有没有感染的人 ,如果它会被传染,则变为感染的状态。对于所有感染上的人,因为会被治好,所以变为没有感染的状态。
请注意传染的顺序:传染是发生在治好之前的,并且新被传染的人不会被治好。即先由感染者传染,再对原感染者染者消除感染,新感染者不会消除感染。
Solution
直接模拟即可。
D2T2
Description
b6e0看见了一段楼梯。这段楼梯有 阶,他现在在第 阶,他想要到第 阶。
对于所有 ( ),可以从第 阶~第 阶直接跨到第 阶。每一阶还有一个值 ,表示从 到 ( )花费 的体力。特殊地, 。
b6e0想知道他到达第 阶花费的最少体力是多少。
Solution
从宏观上来讲,这是一道简单的 题。
状态设计: : 从 号台阶爬到第 号台阶所耗的体力的最小值。
状态转移: 。
此时,若暴力转移时间复杂度会达到 。于是,我们考虑优化。
优化1: 单调队列(by b6e0)
可以发现,每次的区间的变化相当于窗口的滑动,于是我们可以使用单调队列。
优化2: 线段树(by ducati)
记 。对于每次转移,我们查询区间 中各 的最小值,相当于线段树的区间查询。查询后,我们在第 个位置加入 (即在区间 中各数均加 ),相当于线段树的单点修改。
时间复杂度: 。
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
给定拥有 个数的序列 。定义 表示在序列 中找出拥有 个元素的子序列,并将它们的积求和。
例如,当 { }, 。原因如下: 所有的长度为 的子序列共有3个,分别是{ },{ },{ },它们的乘积之和为 。
现在,凉心的Ducati会 次询问你 的值。
由于答案可能过大,请将答案对 取模。
Solution
Subtask 1-3
直接暴力即可。
Subtask 4-5
事实上这题的难度并不大,但是一些选手为什么会被几个Σ吓跑
我们考虑一下,这题的重点——
①找出
,显然要预处理。
②快速求得
。
首先,我们考虑②——即,假设①已经处理完了,我们怎么快速求出 呢?
一个套路——分别求贡献。对于第 个位置,它对答案贡献的次数为 次——即,左端点有 种,右端点有 种,将它们两两搭配形成的区间会包含第 个位置分别一次。
所以, 。
于是,下面的难点在于如何预处理出 。
考虑 。状态设计 表示,目前从 到 看到的这些数中,任选 个不同的数, 即为所有这样组合的积之和。
如, ,那么 。
于是,怎么状态转移呢?式子很简单:
它非常好理解,只需要举个例子就可以啦~
同样,设 ,我们现在想要得到 。
首先,已经得到 , 。
那么,
。
显然 。直接运用公式 即可。
估一下时间复杂度:
①预处理出
,代价为
;
②每次询问中,求
的值,代价为
。
所以,总时间复杂度为 ,可以通过。
#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
求在 的倍数中,一个数最多能有多少个二进制 。
Solution
Subtask 1
显然,我们只需要暴力枚举从 到 。每次判断它是否为 的倍数,并求出其二进制 的数量即可。
时间复杂度: 。
Subtask 2-3
考虑图论建模。
显然,从 到 中,在其二进制表示下末尾多了一个 ,没有多 。故我们可以从 到 连一条权值为0的边。
同时,从 到 中,在其二进制表示下末尾多了一个 。故我们可以从 到 连一条权值为1的边。
为了方便,我们需要把所有节点的编号对 取模。此时答案即为 到 的最短路径。
用双端队列快速求出其最短路径即可。
时间复杂度: 。
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
撒花✿✿ヽ(°▽°)ノ✿撒花