A - Two Rival Students
题意:共n个人排一排,两个人,位于a,b,相邻位置交换至多x次,最大化abs(a-b)的值。
题解:每次交换至多+1,不能超过n-1。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t;
scanf("%d", &t);
while(t--) {
int n, x, a, b;
scanf("%d%d%d%d", &n, &x, &a, &b);
printf("%d\n", min(n - 1, abs(a - b) + x));
}
return 0;
}
B - Magic Stick
题意:给一个数a,有无限次的两种操作:1、若a是偶数,则加上a的一半。2、若a>1,则减去1。问是否可以从x构造出y。
题解:有无限次减法,就看1操作能到达哪里就可以。首先1是不能动的,y<=1。其次,2可以到3,3可以到2,没办法突破到4,所以x=2或x=3时,y<=3。而4可以有4->6->9->8->12->18->27->26->39->...这样无限做下去,所以4以上必有解。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t;
scanf("%d", &t);
while(t--) {
ll x, y;
scanf("%lld%lld", &x, &y);
bool suc = 0;
if(x == 1)
suc = (y <= 1);
else if(x <= 3)
suc = (y <= 3);
else
suc = 1;
puts(suc ? "YES" : "NO");
}
return 0;
}
C - Dominated Subarray
题意:n(n<=2e5)个数的序列a,ai<=n,找最短的“被支配子区间”,一个子区间被支配有两个条件:1、长度>=2,2、有唯一的众数。
题解:一开始还想二分,二分长度然后验证,还写了一个验证的算法,可以均摊O(1)转移出众数(每次尺取的时候,众数至多变化1)。但是转头一想,小的区间不行大的未必不行的。比如我构造一个这样的:
1
7
2 2 4 4 2 2 4
假如一开始二分到4,则验证失败,任意一个子区间的众数都是2和4两个。但最短子区间长度是2。
后来想了一下,是不是最开始考虑的,是否是最短的两个相邻相同元素的差呢?简单证明之后发现的确是这样。
假如最短的两个相邻相同元素为下面的样子:
x,y1,y2,y3,...,yk,x
那么yi互不相等,也当然和x不相等,否则有更短的yi,yj与假设矛盾。则这个区间一定是“被支配子区间”。
但为什么最短“被支配子区间”一定是这样呢?
从x往两边扩展,长度肯定不会更短,而往内收缩,则不会有更短的“被支配子区间”,所以这样的相邻x组成的为被支配子区间的构成元素,长的可以由若干个小的组合得到。在这个算法下n=1不需要特判。注意别被卡memset。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, a[200005];
int pre[200005];
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t;
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
memset(pre, -1, sizeof(pre[0]) * (n + 1));
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
int minlen = 1e9;
for(int i = 1; i <= n; ++i) {
if(pre[a[i]] != -1)
minlen = min(minlen, i - pre[a[i]] + 1);
pre[a[i]] = i;
}
if(minlen == 1e9)
minlen = -1;
printf("%d\n", solve());
}
return 0;
}
但是貌似t只有1000,在cf是卡不了memset的样子。
二分的错误算法,不过算是知道怎么O(1)转移众数了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, a[200005];
int occ[200005];
int maxi[200005];
bool check(int len) {
memset(occ, 0, sizeof(occ[0]) * (n + 1));
memset(maxi, 0, sizeof(maxi[0]) * (n + 1));
int maxtop = 0;
for(int i = 1; i <= len; ++i) {
--maxi[occ[a[i]]];
++occ[a[i]];
++maxi[occ[a[i]]];
maxtop = max(maxtop, occ[a[i]]);
}
if(maxi[maxtop] == 1)
return 1;
else {
for(int i = len + 1; i <= n; ++i) {
--maxi[occ[a[i]]];
++occ[a[i]];
++maxi[occ[a[i]]];
--maxi[occ[a[i - len]]];
--occ[a[i - len]];
++maxi[occ[a[i - len]]];
while(maxi[maxtop] == 0)
--maxtop;
maxtop = max(maxtop, occ[a[i]]);
if(maxi[maxtop] == 1)
return 1;
}
}
return 0;
}
int solve() {
int l = 2, r = n;
//printf("l=%d r=%d\n", l, r);
while(1) {
int m = l + r >> 1;
printf("m=%d\n", m);
if(l == m) {
if(check(l))
return l;
else if(check(r))
return r;
else
return -1;
}
if(check(m))
r = m;
else
l = m + 1;
}
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
int t;
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
if(n == 1) {
puts("-1");
continue;
}
printf("%d\n", solve());
}
return 0;
}
D - Yet Another Monster Killing Problem
题意:有n个怪物,从左到右刷,每个怪攻击力ai,你有m个英雄,每个英雄有攻击力pi和耐力si。每天你放一个英雄往前刷,直到耐力消耗完/怪刷完/遇到攻击比他高的怪,他就会回城。英雄可以按任意顺序用任意次,无解输出-1。
题解:一开始想的是,肯定是个偏序关系,有用的英雄要么攻击高,要么耐力高,组成一条折线(一开始还在想凸壳),然后每一天就贪心尽可能往前走,由于英雄被筛选过,所以往前走d步要找的是si>=d的第一个英雄。找不到就-1。找得到就比较两者攻击力,攻击力不够也是-1,这样貌似变成一个RMQ问题。
所以排序筛选之后贪心,二分往前走的值,然后用ST表RMQ,是严格的O(nlogn)。
注意ST表的访问不能够越界!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Hero {
int s, p;
bool operator<(const Hero &h)const {
return s != h.s ? s < h.s : p < h.p;
}
} h[200005], H[200005];
int n, hn, a[200005];
int st[200005], stop;
int id[200005];
//耐力超过i的攻击力最高的英雄的编号为id[i];
int cur;
class ST_Table {
private:
static const int MAXLOGN = 19;
static const int MAXN = 200005;
int logn[MAXN + 5];
int f[MAXN + 5][MAXLOGN + 1];
public:
inline void init1() {
logn[1] = 0;
for(int i = 2; i <= MAXN; i++) {
logn[i] = logn[i / 2] + 1;
}
}
inline void init2(int n) {
for(int i = 1; i <= n; i++)
f[i][0] = a[i];
for(int j = 1; j <= MAXLOGN; j++)
for(int i = 1; i + (1 << j) - 1 <= n; i++)
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
inline int range_max(int l, int r) {
int s = logn[r - l + 1];
return max(f[l][s], f[r - (1 << s) + 1][s]);
}
} ST;
bool check(int len) {
int p = id[len];
if(p == -1)
return 0;
else
return H[p].p >= ST.range_max(cur + 1, min(n, cur + len));
}
int solve() {
int l = 1, r = H[hn].s;
while(1) {
int m = l + r >> 1;
if(l == m) {
if(check(r))
return r;
else if(check(l))
return l;
else
return -1;
}
if(check(m))
l = m;
else
r = m - 1;
}
}
int main() {
#ifdef KisekiPurin
freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
ST.init1();
int t;
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
ST.init2(n);
scanf("%d", &hn);
for(int i = 1; i <= hn; ++i) {
scanf("%d%d", &h[i].p, &h[i].s);
}
sort(h + 1, h + 1 + hn);
stop = 0;
for(int i = 1; i <= hn; ++i) {
while(stop && h[i].p >= h[st[stop]].p)
--stop;
st[++stop] = i;
}
hn = stop;
for(int i = 1; i <= hn; ++i)
H[i] = h[st[i]];
for(int i = 1, curid = 1; i <= n; ++i) {
while(curid <= hn && H[curid].s < i)
++curid;
if(H[curid].s >= i)
id[i] = curid;
else
id[i] = -1;
}
cur = 0;
int ans = 0;
while(cur < n) {
int d = solve();
if(d == -1) {
ans = -1;
break;
} else {
cur += d;
++ans;
}
}
printf("%d\n", ans);
}
return 0;
}
注:要筛选出一个有用的偏序集合的时候,貌似是排序然后单调栈是最好理解的。在这题中,因为维度s的取值范围足够小,可以不进行排序而使用dp来进行转移,具体做法就是在每个耐力值上打上最大的pi标记,然后从n向1传递pi,这样形成的就是一个偏序集合,复杂度O(n+m)。
原文地址:https://www.cnblogs.com/KisekiPurin2019/p/11854682.html