C. Combination of Physics and Maths
题意:
一个矩阵的底面积 (S) 定义为最后一行的数的和,重量 (F) 定义为所有数的和,给一个正整数矩阵,找一个“压强“ (p=frac{F}{S}) 最大的可非连续子矩阵,输出 (p)。
思路:
当底面积确定时,重量越大,压强越大,所以子矩阵的顶部一定是原矩阵的顶部。不确定的是子矩阵的底在哪一行,并且子矩阵选择哪几列。那么枚举每一行作为底,依次选择贡献最大的列直到答案无法变大。如何计算贡献?用前缀和求出 (sum[i][j] = sum_{k=1}^isum[k][j]) ,那么在枚举 到某一行 (i) 时,第 (j) 列的贡献为 (sum[i][j]/a[i][j]) ,按贡献大小 sort 一下,依次选择即可。
复杂度为 (O(Tnmlogm))。
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define vi vector<int>
#define SZ(x) (int)x.size()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
int a[205][205];
int sum[205][205];
bool cmp(pii x, pii y) {
return 1ll * x.fi * y.se > 1ll * x.se * y.fi;
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
for(int j = 1; j <= m; j++)
for(int i = 1; i <= n; i++)
sum[i][j] = sum[i - 1][j] + a[i][j];
ll afz = 0, afm = 1;
for(int i = 1; i <= n; i++) {
vector<pii> tmp;
for(int j = 1; j <= m; j++)
tmp.pb(mp(sum[i][j], a[i][j]));
sort(tmp.begin(), tmp.end(), cmp);
ll fz = tmp[0].fi, fm = tmp[0].se;
for(int j = 1;j<SZ(tmp);j++) {
ll tmpz = fz + tmp[j].fi;
ll tmpm = fm + tmp[j].se;
if(fz * tmpm < fm * tmpz) {
fz = tmpz;
fm = tmpm;
} else
break;
}
if(afz * fm < afm * fz) {
afz = fz;
afm = fm;
}
}
printf("%.8f
", 1.0 * afz / afm);
}
}
J. Josephus Transform
题意:
给一个长度为 n 的排列和 m 次操作,每个操作可以表示为 (k, x),即进行 x 次以 k-约瑟夫变
换。问最后排列长啥样。
思路:
对于一次k-约瑟夫变换,我们需要知道它的变化序列,如【1,2,3,4,5】的3-约瑟夫变换序列是【3,1,5,2,4】。
如何求变化序列?
设上一个被取出来的数字是当时的第 pos 个(初始设为 1 ),当前还剩下 cnt 个数字,那么下一个被选出来的数应该是当前剩下的所有数字中的第 【((pos+k-2)\% cnt + 1) 】个。可以利用树状数组或线段树处理出这个变化序列,复杂度为 (O(nlogn))。
接着对于x次的k-约瑟夫变换,容易发现变换是满足结合律的,所以可以自己写一个快速幂,复杂度为 (O(nlogx))。
所以对于m次查询,总的复杂度为(O(m(nlogn+nlogx))=O(nm(logn+logx)))。
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define vi vector<int>
#define SZ(x) (int)x.size()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int MAXN = 1e5 + 5;
int n, m;
int lowbit(int x) {
return x & (-x);
}
vi multi(vi x, vi y) {
vi tmp(n + 1);
for(int i = 1; i <= n; i++)
tmp[i] = x[y[i]];
return tmp;
}
vi qpow(vi a, int x) {
vi ans(n + 1);
for(int i = 1; i <= n; i++)
ans[i] = i;
while(x) {
if(x & 1)
ans = multi(ans, a);
a = multi(a, a);
x >>= 1;
}
return ans;
}
int main() {
scanf("%d%d", &n, &m);
vi ans(n + 1);
for(int i = 1; i <= n; i++)
ans[i] = i;
while(m--) {
vi a(n + 1), c(n + 1);
for(int i = 1; i <= n; i++)
for(int j = i; j <= n; j += lowbit(j))
a[j]++;
int k, x;
scanf("%d%d", &k, &x);
int pos = 1;
for(int i = n; i >= 1; i--) {
pos = (pos + k - 2) % i + 1;
int L = 1, R = n;
while(L < R) {
int mid = (L + R) / 2;
int sum = 0;
for(int j = mid; j >= 1; j -= lowbit(j))
sum += a[j];
if(sum < pos)
L = mid + 1;
else
R = mid;
}
c[n - i + 1] = L;
for(int j = L; j <= n; j += lowbit(j))
a[j]--;
}
ans = multi(ans, qpow(c, x));
}
for(int i = 1; i <= n; i++)
printf("%d ", ans[i]);
}
K. K-Bag
题意:
k-bag 序列定义为由多个 1~ k 的排列顺序连接起来的序列。
题目询问给定序列是不是 k-bag 的连续子序列。
思路:
定义一个长度为 k 的 k-bag 为最小 k-bag,即1~k的某种排列。则合法的给定序列应该是由
【最小k-bag的一部分(头部)+ 0至多个最小k-bag(中部) + 最小k-bag的一部分(尾部)】组成的。
首先预处理一些变量:
用 ex[i] 表示到 i 为止的 k 个数中(i<k 则为 i 个)是否有重复的数,有则 ex[i] = 0,否则 ex[i] = 1。
用 sum[i] 表示前缀和,用 maxx[i] 表示前缀最大值。
预处理的复杂度为 (O(n))。
用 dp[i] 表示 i 是否可以作为合法的结束位置。
若 i 是”头部“的合法结束位置,应满足 ex[i] = 1 && maxx[i] <= k,合法则 dp[i] = 1。
若 i 是”中部“的合法结束位置,应满足 ex[i] = 1 && dp[i-k] = 1 && sum[i] - sum[i-k] == k*(k+1)/2,合法则 dp[i] = 1。
若 i 是”尾部“的合法结束位置,与头部类似,反向处理一遍即可,合法则 dp2[i] = 1。
因此总的复杂度还是 (O(n))。
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define vi vector<int>
#define SZ(x) (int)x.size()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int MAXN = 5e5 + 5;
int n, k;
int a[MAXN];
int ex[MAXN];
unordered_map<int, int>num;
int dp[MAXN];
int maxx[MAXN];
ll sum[MAXN];
void solve1() {
int cnt = 0;
for(int i = 1; i <= min(n, k); i++) {
num[a[i]]++;
if(num[a[i]] == 2)
cnt++;
if(cnt > 0)
ex[i] = 0;
}
for(int i = min(n, k) + 1; i <= n; i++) {
if(i - k >= 1) {
num[a[i - k]]--;
if(num[a[i - k]] == 1)
cnt--;
}
num[a[i]]++;
if(num[a[i]] == 2)
cnt++;
if(cnt > 0)
ex[i] = 0;
}
for(int i = 1; i <= n; i++) {
maxx[i] = max(maxx[i - 1], a[i]);
sum[i] = sum[i - 1] + 1ll * a[i];
}
}
int ex2[MAXN];
unordered_map<int, int>num2;
int maxx2[MAXN];
int dp2[MAXN];
void solve2() {
int cnt = 0;
for(int i = n; i >= 1 && n - i + 1 <= k; i--) {
num2[a[i]]++;
if(num2[a[i]] == 2)
cnt++;
if(cnt > 0)
ex2[i] = 0;
}
for(int i = n; i >= 1; i--)
maxx2[i] = max(maxx2[i + 1], a[i]);
}
void init(int n) {
for(int i = 0; i < n + 5; i++) {
ex[i] = ex2[i] = 1;
dp[i] = maxx[i] = sum[i] = 0;
dp2[i] = maxx2[i] = 0;
}
num.clear();
num2.clear();
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &k);
init(n);
for(int i = 1; i <= n; i++)
scanf("%d", a + i);
solve1();
for(int i = 1; i <= min(n, k); i++)
if(ex[i] == 1 && maxx[i] <= k)
dp[i] = 1;
dp[0] = 1;
for(int i = min(n, k) + 1; i <= n; i++) {
int f = ex[i];
if(i - k >= 0) {
if(dp[i - k] == 0)
f = 0;
if((sum[i] - sum[i - k]) != 1ll * k * (k + 1) / 2)
f = 0;
}
if(f)
dp[i] = 1;
}
solve2();
dp2[n + 1] = 1;
for(int i = n; i >= 1 && n - i + 1 <= k; i--)
if(ex2[i] == 1 && maxx2[i] <= k)
dp2[i] = 1;
int ans = 0;
for(int i = 1; i <= n; i++)
if(dp[i] && dp2[i + 1])
ans = 1;
if(ans)
printf("YES
");
else
printf("NO
");
}
}