复旦勰码 3 月月赛 II Div2 题解
P10251 农场
所有矩形左侧的边的横坐标的最小值即为圈地左侧的边的横坐标的最小值,其他边同理。注意题目并不满足 \(x1<x2,y1<y2\)。
剑指千星,问道苍冥。
#include <bits/stdc++.h>
#define int long long
int n,X1 = 1e9,Y1 = 1e9,X2 = -1e9,Y2 = -1e9;
signed main(){
scanf("%lld",&n);
for(int i = 1,x1,x2,y1,y2;i <= n;i ++){
scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
X1 = std::min(X1,std::min(x1,x2)),Y1 = std::min(Y1,std::min(y1,y2));
X2 = std::max(X2,std::max(x1,x2)),Y2 = std::max(Y2,std::max(y1,y2));
}
int num = (X2 - X1) * (Y2 - Y1);
printf("%lld",num);
return 0;
}
P10252 线性变换
我的思路是特判一些情况:\(x=0\),\(a=0\),\(b=0\),\(ax-b\ge x\),\(a=1\),其他的情况就是 \(a\ge 2\) 且每次一定会让值减小的情况了,并且复杂度正确。
由于此时我们已经满足 \(ax-b<x\),当 \(ax-b\) 成为了新的 \(x\) 之后,下一次就会变为 \(a(ax-b)-b<ax-b\),因此一定会递减。
我们考虑最极限数据的时间复杂度,即当 \(a=2,x=10^9\) 时,此时要满足 \(ax-b<x\),\(b\) 的最小值应该是 \(10^9+1\),然后 \(x\) 变为 \(10^9-1\),接着是 \(10^9-3\),\(10^9-7\),\(10^9-15\),可见被减数是呈指数级别增长的,因此复杂度正确。
雪境化三清域外,寸心系尘世万千。
#include <bits/stdc++.h>
#define int long long
int T,x,a,b;
signed main(){
scanf("%lld",&T);while(T --){
scanf("%lld%lld%lld",&x,&a,&b);
if(x == 0 or a == 0) printf("%lld\n",-b);
else if(b == 0) printf("%lld\n",x);
else if(a * x - b >= x) printf("%lld\n",x);
else if(a == 1) printf("%lld\n",x - (x / b + 1) * b);
else{
int num = a * x - b;
while(1){
if(num < 0){printf("%lld\n",num);break;}
num = num * a - b;
}
}
}
return 0;
}
P10253 说唱
假设 \(x\) 为 \(\overline{abcd}\),那么 \(y=abcd+abc+ab+a=(1000a+100b+10c+d)+(100a+10b+c)+(10a+b)+(a)=1111a+111b+11c+d\)。
于是我们容易得到一个结论:\(10x=9y+S\),\(S=a+b+c+d\),即 \(x\) 所有位的数字之和。那么 \(9y=10x-S\)。
因此我们先算出来 \(9y\),然后从 \(9y/10\) 开始依次 \(+1\) 判断是否合法,直到算出来的 \(10x-S\ge9y\) 为止停止。由于 \(S\) 最大不会超过 \(9n\),因此整体复杂度不会超时,而且也跑不满。
活下去...为了和我们一样的漂泊者,不再别离。
#include <bits/stdc++.h>
#define N 500005
using namespace std;
int T,a[N];string s;
int main(){
scanf("%d",&T);while(T --){
cin >> s;if(s == "0") {puts("0");continue;}
s = "0" + s;int c = 0,num = 0;
for(int i = s.size() - 1;i >= 0;i --){
int J = s[i] - '0';
s[i] = (J * 9 + c) % 10 + '0';
c = (J * 9 + c) / 10;
}
if(s[0] == '0') s.erase(0,1);int cnt = (s.size() - 1) * 9;
for(int i = 0;i < s.size() - 1;i ++) a[i + 1] = s[i] - '0',num -= a[i + 1];
num -= s[s.size() - 1] - '0';
while(cnt --){
a[s.size() - 1] ++;num -= 1;
for(int i = s.size() - 1;i >= 1;i --){
if(a[i] != 10) break;
a[i] = 0,a[i - 1] ++,num += 9;
}
num += 10;
if(num >= 0) break;
}
if(num == 0){for(int i = 1;i <= s.size() - 1;i ++) printf("%d",a[i]);puts("");}
else puts("-1");
}
return 0;
}
P10254 口吃
前置:排序不等式。
内容:设有两列数 \(a_1,a_2,…,a_n\) 和 \(b_1,b_2,…,b_n\),满足 \(a_1\le a_2\le …\le a_n\),\(b_1\le b_2\le …\le b_n\)。\(j_1,j_2,…,j_n\) 是 \(1,2,…,n\) 的一个排列,则有:
\(\begin{aligned}\sum\limits_{i=1}^n a_ib_{n+1-i}\le \sum\limits_{i=1}^n a_ib_{j_i}\le \sum\limits_{i=1}^n a_ib_i\end{aligned}\)。当且仅当 \(a_i=a_j\) 或 \(b_i=b_j(1\le i,j\le n)\) 时等号成立。
语言描述:逆序和 \(\le\) 乱序和 \(\le\) 顺序和。
证明:不妨设 \(j_n\ne n\)(若 \(j_n=n\) 则考虑 \(j_{n-1}\)),第 \(m\) 项 \(a_mb_{j_m}\) 中 \(j_m=n\)。
在乱序和 \(S\) 中,我们调整第 \(n\) 项和第 \(m\) 项的位置,易知 \(a_nb_n+a_mb_{j_n}\ge a_nb_{j_n}+a_mb_{j_m}\),故新的和 \(S_1\ge S\)。
可能上面的式子有点不太易知,我们移项得到 \(\Delta=a_nb_n-a_mb_{j_m}+(a_m-a_n)b_{j_n}\),由于 \(b_n\ge b_{j_m}\),因此 \(\Delta\ge a_nb_{j_m}-a_mb_{j_m}+(a_m-a_n)b_{j_n}\),合并同类项,可得 \(\Delta\ge (a_n-a_m)(b_{j_m}-b_{j_n})\),因此 \(\Delta\ge 0\),因此 \(S_1\ge S\)。
同理调整第 \(n-1\) 项,得到 \(S_2\ge S_1\ge S\),最多经过 \(n-1\) 次调整可得:\(a_1b_1+a_2b_2+…+a_nb_n\ge a_1b_{j_1}+a_2b_{j_2}+…+a_nb_{j_n}\)。
显然,当且仅当 \(a_i=a_j\) 或 \(b_i=b_j(1\le i,j\le n)\) 时等号成立。因此不等式右侧得证,左侧同理。
证毕。
那么本题中,我们可以根据排序不等式得到所有排列中最大的权值是 \(mx=1^2+2^2+…+n^2\)。
写到这里,我突然发现,好像用不到排序不等式,不需要知道那个 \(mx\) 一定是最大的权值,但是既然已经写完了,那就当作给大家补充个知识了,不想删掉了,毕竟写了半天。
考虑对于一个排列 \(p\) 进行冒泡排序:若 \(p_i\ge p_{i+1}\),则交换 \(p_i\) 与 \(p_{i+1}\)。那么这一次交换会让这个排列的权值增加 \([p_i\times (i+1)+p_{i+1}\times i]-[p_i\times i+p_{i+1}\times (i+1)]=p_i-p_{i+1}\)。
一个排好序的排列权值为 \(\begin{aligned}\sum\limits_{i}i^2-\sum\limits_{i<j,p_i>p_j}(p_i-p_j)\end{aligned}\)。
那么现在我们关键就在于求出这个 \(\begin{aligned}\sum\limits_{i<j,p_i>p_j}(p_i-p_j)\end{aligned}\)。我们把它拆开来算,拆成 \(\begin{aligned}\sum\limits_{i<j,p_i>p_j}p_i\end{aligned}\) 和 \(\begin{aligned}\sum\limits_{i<j,p_i>p_j}p_j\end{aligned}\) 两部分,相减。两部分同理。我们将考虑如何求出前者。
首先呢,我们需要先做一下这个题:P1521 求逆序对。即求出 \(1\sim n\) 的排列中逆序对数恰好为 \(K\) 的排列的个数。
我们定义 \(g_{i,j}\) 表示 \(1\sim i\) 的全排列里逆序对数为 \(j\) 的数量。我们知道,我们目前要在排列里面插入 \(i\) 这个数字,那么排在 \(i\) 后面的有多少个数字,那么就新增多少个逆序对。因此我们可以得到状态转移方程:
\(\begin{aligned}g_{i,j}=\sum\limits_{k=max(0,j-i+1)}^j g_{i-1,k}\end{aligned}\)。初始化:\(g_{i,0}=1(0\le i\le n)\)。于是我们得到了 P1521 的 AC 代码:
空山无归,春风不渡。
#include <bits/stdc++.h>
#define N 5005
#define MOD 10000
using namespace std;
int n,K,g[N][N];
int main(){
scanf("%d%d",&n,&K);g[0][0] = 1;
for(int i = 1;i <= n;i ++){
g[i][0] = 1;
for(int j = 1;j <= K;j ++){
for(int k = max(0,j - i + 1);k <= j;k ++){
g[i][j] = (g[i][j] + g[i - 1][k]) % MOD;
}
}
}
printf("%d",g[n][K]);
return 0;
}
回到本题,我们定义 \(g_{i,j}\) 同上,\(f_{i,j}\) 表示 \(1\sim i\) 的全排列里逆序对数为 \(j\) 时所有 \(g_{i,j}\) 个满足条件的排列的 \(\begin{aligned}\sum\limits_{i<j,p_i>p_j}p_i\end{aligned}\)。有如下转移:
\(\begin{aligned}f_{i,j}=\sum\limits_{k=max(0,j-i+1)}^j f_{i-1,k}+g_{i-1,k}\times i\times (j-k)\end{aligned}\)。
前者好说,主要是后者。我们目前仍然是将 \(i\) 插入到前 \(i-1\) 个数组成的排列中。根据我们上面推出来的结论:若 \(p_i\ge p_{i+1}\),则交换 \(p_i\) 与 \(p_{i+1}\)。那么这一次交换会让这个排列的权值增加 \([p_i\times (i+1)+p_{i+1}\times i]-[p_i\times i+p_{i+1}\times (i+1)]=p_i-p_{i+1}\)。我们可以知道将 \(i\) 插入到 \(k\) 位置上中我们会增加 \(j-k\) 的逆序对,因为我们现在只算 \(p_i\) 的贡献,那么我们的贡献此时就相当于 \(i\times (j-k)\),总贡献就在乘上 \(g_{i-1,k}\)。
简单说一下求 \(p_j\) 的贡献吧。我们令 \(dp_{i,j}\) 表示 \(n-i+1\sim n\) 的全排列里逆序对数为 \(j\) 时所有 \(g_{i,j}\) 个满足条件的排列的 \(\begin{aligned}\sum\limits_{i<j,p_i>p_j}p_j\end{aligned}\)。有如下转移:
\(\begin{aligned}dp_{i,j}=\sum\limits_{k=max(0,j-i+1)}^j dp_{i-1,k}+g_{i-1,k}\times (n-i+1)\times (j-k)\end{aligned}\)。
与 \(f_{i,j}\) 不同的时,\(f_{i,j}\) 我们时从 \(1\) 开始依次向后将每个数插入到排列当中求贡献,而 \(dp_{i,j}\) 是从 \(n\) 开始依次向前将每个数插入到当前排列中求贡献。此时我们需要注意到一个点:我们整个过程是让一个已经排好序的排列交换一些以达到使逆序对数量为 \(K\),也就是想我们什么情况下会减去这个 \(q_j\),实在序列原本有序的时候,交换两个数产生了一个逆序对才会产生这个贡献,那么此时我们应该求得是将 \(n-i+1\) 插入到 \(k\) 这个位置上会产生的顺序对的数量,为 \(j-k\),这样才会使得可以让他们交换后使得逆序对数量为 \(j\)。
最后结果为 \(mx\times g_{n,K}-(f_{n,K}-dp_{n,K})\)。注意取模问题。
此时我们的暴力代码就已经成型了:
他们的呼声,遗落为深谷中冗长的回音。
#include <bits/stdc++.h>
#define int long long
#define MOD 998244353
#define N 5005
using namespace std;
int n,K,num,ans,g[N][N],f[N][N],dp[N][N];
signed main(){
scanf("%lld%lld",&n,&K);g[0][0] = 1;
for(int i = 1;i <= n;i ++){
g[i][0] = 1;
for(int j = 1;j <= K;j ++){
for(int k = max(0ll,j - i + 1);k <= j;k ++){
g[i][j] = (g[i][j] + g[i - 1][k]) % MOD;
}
}
}
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= K;j ++){
for(int k = max(0ll,j - i + 1);k <= j;k ++){
f[i][j] = (f[i][j] + f[i - 1][k] + g[i - 1][k] * i * (j - k)) % MOD;
}
}
}
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= K;j ++){
for(int k = max(0ll,j - i + 1);k <= j;k ++){
dp[i][j] = (dp[i][j] + dp[i - 1][k] + g[i - 1][k] * (n - i + 1) * (j - k)) % MOD;
}
}
}
for(int i = 1;i <= n;i ++) num += i * i;
ans = ((num * g[n][K] % MOD + dp[n][K]) % MOD - f[n][K] + MOD) % MOD;
printf("%lld",ans);
return 0;
}
然后呢,我们就使用前缀和优化一下,可以自己尝试找一下优化的方法,这里提供一种方法:
不想解释了,这个东西完全可以自己想出来怎么去优化,因为我们的 \(k\) 是满足 \(k=max(0,j-i+1)\),可以由上一个状态转移过来。
未曾被上苍注视的生命,亦将名姓写在天地之间。
#include <bits/stdc++.h>
#define int long long
#define MOD 998244353
#define N 5005
using namespace std;
int n,K,num,ans,g[N][N],f[N][N],dp[N][N],s[N],cnt = -1;
signed main(){
scanf("%lld%lld",&n,&K);g[0][0] = 1;
for(int i = 1;i <= n;i ++){
g[i][0] = 1,cnt = -1;
for(int j = 1;j <= K;j ++){
g[i][j] = (g[i][j - 1] + g[i - 1][j]) % MOD;
if(j - i + 1 > 0){
cnt ++;
g[i][j] = (g[i][j] - g[i - 1][cnt] + MOD) % MOD;
}
}
}
for(int i = 1;i <= n;i ++){
s[0] = 1 * i,cnt = -1;
for(int j = 1;j <= K;j ++){
f[i][j] = (f[i][j - 1] + f[i - 1][j] + s[j - 1]) % MOD;
s[j] = (s[j - 1] + g[i - 1][j] * i) % MOD;
if(j - i + 1 > 0){
cnt ++;
f[i][j] = (f[i][j] - (f[i - 1][cnt] + g[i - 1][cnt] * i * i) % MOD + MOD) % MOD;
s[j] = (s[j] - (g[i - 1][cnt] * i) % MOD + MOD) % MOD;
}
}
}
for(int i = 1;i <= n;i ++){
s[0] = 1 * (n - i + 1),cnt = -1;
for(int j = 1;j <= K;j ++){
dp[i][j] = (dp[i][j - 1] + dp[i - 1][j] + s[j - 1]) % MOD;
s[j] = (s[j - 1] + g[i - 1][j] * (n - i + 1)) % MOD;
if(j - i + 1 > 0){
cnt ++;
dp[i][j] = (dp[i][j] - (dp[i - 1][cnt] + g[i - 1][cnt] * (n - i + 1) * i) % MOD + MOD) % MOD;
s[j] = (s[j] - (g[i - 1][cnt] * (n - i + 1)) % MOD + MOD) % MOD;
}
}
}
for(int i = 1;i <= n;i ++) num += i * i;
ans = ((num * g[n][K] % MOD + dp[n][K]) % MOD - f[n][K] + MOD) % MOD;
printf("%lld",ans);
return 0;
}
但是此时还是不满足空间要求的。我们发现状态 \(i\) 只会被 \(i-1\) 影响,因此滚动数组优化即可。然后我们就可以 AC 了:
修真者的仙途,何以由凡人的牺牲铸就?
#include <bits/stdc++.h>
#define int long long
#define MOD 998244353
#define N 45005
using namespace std;
int n,K,num,ans,g[2][N],f[2][N],dp[2][N],s[N],cnt = -1,fl;
signed main(){
scanf("%lld%lld",&n,&K);g[0][0] = 1;
for(int i = 1;i <= n;i ++){
fl ^= 1;
g[fl][0] = 1,cnt = -1;
for(int j = 1;j <= K;j ++){
g[fl][j] = (g[fl][j - 1] + g[fl ^ 1][j]) % MOD;
if(j - i + 1 > 0){
cnt ++;
g[fl][j] = (g[fl][j] - g[fl ^ 1][cnt] + MOD) % MOD;
}
}
s[0] = 1 * i,cnt = -1;
for(int j = 1;j <= K;j ++){
f[fl][j] = (f[fl][j - 1] + f[fl ^ 1][j] + s[j - 1]) % MOD;
s[j] = (s[j - 1] + g[fl ^ 1][j] * i) % MOD;
if(j - i + 1 > 0){
cnt ++;
f[fl][j] = (f[fl][j] - (f[fl ^ 1][cnt] + g[fl ^ 1][cnt] * i * i) % MOD + MOD) % MOD;
s[j] = (s[j] - (g[fl ^ 1][cnt] * i) % MOD + MOD) % MOD;
}
}
s[0] = 1 * (n - i + 1),cnt = -1;
for(int j = 1;j <= K;j ++){
dp[fl][j] = (dp[fl][j - 1] + dp[fl ^ 1][j] + s[j - 1]) % MOD;
s[j] = (s[j - 1] + g[fl ^ 1][j] * (n - i + 1)) % MOD;
if(j - i + 1 > 0){
cnt ++;
dp[fl][j] = (dp[fl][j] - (dp[fl ^ 1][cnt] + g[fl ^ 1][cnt] * (n - i + 1) * i) % MOD + MOD) % MOD;
s[j] = (s[j] - (g[fl ^ 1][cnt] * (n - i + 1)) % MOD + MOD) % MOD;
}
}
}
for(int i = 1;i <= n;i ++) num += i * i;
ans = ((num * g[fl][K] % MOD + dp[fl][K]) % MOD - f[fl][K] + MOD) % MOD;
printf("%lld",ans);
return 0;
}