题目1 : 循环数组
考虑枚举从位置i断开,移到前面。那么需要确保从当前位置i开始的前缀和都大于0。记此时i到n的总和为x,那么若x大于从1开始到i-1的前缀和中的最小值,则i必然可行。
所以需要维护的东西有,从i位置开始到n的前缀和,为了支持i到i+1的数值变化,需要用一个支持区间加法的线段树。维护从1开始的前缀和的最小值可以用前缀和数组来完成,就酱。
#define others
#ifdef poj
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#endif // poj
#ifdef others
#include <bits/stdc++.h>
#endif // others
//#define file
#define all(x) x.begin(), x.end()
using namespace std;
const double pi = acos(-1.0);
typedef long long LL;
typedef unsigned long long ULL;
void umax(int &a, int b) {
a = max(a, b);
}
void umin(int &a, int b) {
a = min(a, b);
}
void file() {
freopen("a.in", "r", stdin);
// freopen("1.txt", "w", stdout);
}
namespace Solver {
const int maxn = 110000;
int n, A[maxn];
LL pre[maxn], minv[maxn], suf[maxn];
#define lc rt<<1
#define rc rt<<1|1
#define lson l, m, lc
#define rson m+1, r, rc
struct A {
LL add, minv;
}tr[maxn << 2];
LL PushUp(int rt) {
tr[rt].minv = min(tr[lc].minv, tr[rc].minv);
}
LL PushDown(int rt, int len) {
if(tr[rt].add) {
tr[lc].minv += tr[rt].add, tr[rc].minv += tr[rt].add;
tr[lc].add += tr[rt].add, tr[rc].add += tr[rt].add;
tr[rt].add = 0;
}
}
void Add(int L, int R, LL add, int l, int r, int rt) {
if(L > R) return ;
if(L <= l && R >= r) {
tr[rt].add += add;
tr[rt].minv += add;
return ;
}
PushDown(rt, r - l + 1);
int m = (l + r) >> 1;
if(L <= m) Add(L, R, add, lson);
if(R > m) Add(L, R, add, rson);
PushUp(rt);
}
LL Query(int L, int R, int l, int r, int rt) {
if(L <= l && R >= r) {
return tr[rt].minv;
}
PushDown(rt, r - l +1);
int m = (l + r) >> 1;
LL ans = 1e18;
if(L <= m) ans = min(ans, Query(L, R, lson));
if(R > m) ans = min(ans, Query(L, R, rson));
return ans;
}
void solve() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &A[i]);
pre[1] = A[1], minv[1] = A[1];
for(int i = 2; i <= n; i++) {
pre[i] = pre[i-1] + A[i];
minv[i] = min(pre[i], minv[i-1]);
}
for(int i = 1; i <= n; i++)
Add(i, i, pre[i], 1, n, 1);
for(int i = 1; i <= n; i++) {
// cout<<Query(n, n, 1, n, 1)<<" "<<pre[i-1]<<" "<<i<<endl;
if(Query(i, n, 1, n, 1) <= 0) {
Add(i+1, n, -A[i], 1, n, 1);
continue;
} else {
LL v = Query(n, n, 1, n, 1);
if(v + minv[i-1] > 0) {
cout<<i<<endl;
return ;
}
Add(i+1, n, -A[i], 1, n, 1);
}
}
puts("-1");
}
};
int main() {
// file();
Solver::solve();
return 0;
}
题目2 : 座位问题
可以发现每次观众会找到一段连续的空位最多的线段,然后坐在中间。
区间长度为偶数就坐在靠左的位置。拿优先队列或set维护下就可以了。
#define others
#ifdef poj
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#endif // poj
#ifdef others
#include <bits/stdc++.h>
#endif // others
//#define file
#define all(x) x.begin(), x.end()
using namespace std;
const double pi = acos(-1.0);
typedef long long LL;
typedef unsigned long long ULL;
void umax(int &a, int b) {
a = max(a, b);
}
void umin(int &a, int b) {
a = min(a, b);
}
void file() {
freopen("a.in", "r", stdin);
// freopen("1.txt", "w", stdout);
}
namespace Solver {
const int maxn = 110000;
int n, m, k;
int v[maxn];
struct A {
int x, l;
bool operator < (const A & b) const {
return x > b.x || (x == b.x && l < b.l);
}
};
void solve() {
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= m; i++) {
int val;
scanf("%d", &val);
v[val] = 1;
}
int p = 1, cnt = (v[1] == 0);
set<A> st;
for(int i = 2; i <= n; i++) {
if(cnt == 0 && v[i] == 0) p = i;
if(v[i] == 0) cnt++;
else {
st.insert({cnt, p});
cnt = 0;
}
}
if(cnt) st.insert({cnt, p});
for(int i = 1; i <= k; i++) {
A x = *st.begin();
st.erase(st.begin());
int id = (x.l + (x.x-1)/2);
int ed = x.l + x.x - 1;
// cout<<x.l<<" "<<id<<" "<<ed<<" denig "<<endl;
printf("%d\n", id);
// cout<<x.l<<" "
st.insert({id-x.l, x.l});
st.insert({ed - id, id+1});
}
}
};
int main() {
// file();
Solver::solve();
return 0;
}
题目3 : 末尾有最多0的乘积
0的个数只和2还有5的幂次有关。
因此把每个数预处理,然后就是背包问题。
dp[i][j][k] 代表 对于当前决策第i个物品用不用,已经选了j个物品,其中这个乘积包含5的幂次为k的情况下最多包含2的幂次的个数。
显然k最多到1400。
然后枚举选或者不选转移即可。
#define others
#ifdef poj
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#endif // poj
#ifdef others
#include <bits/stdc++.h>
#endif // others
//#define file
#define all(x) x.begin(), x.end()
using namespace std;
const double pi = acos(-1.0);
typedef long long LL;
typedef unsigned long long ULL;
void umax(int &a, int b) {
a = max(a, b);
}
void umin(int &a, int b) {
a = min(a, b);
}
void file() {
freopen("a.in", "r", stdin);
// freopen("1.txt", "w", stdout);
}
namespace Solver {
const int maxn = 111;
int n, m;
int v[maxn], v2[maxn], v5[maxn];
int dp[2][101][1500];
void solve() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &v[i]);
for(int i = 1; i <= n; i++) {
while(v[i]%2==0) v2[i]++, v[i]/=2;
while(v[i]%5==0) v5[i]++, v[i]/=5;
}
LL sum1, sum2; sum1 = sum2 = 0;
memset(dp, -0x3f3f3f3f, sizeof dp);
dp[0][0][0] = 0;
for(int i = 1; i <= n; i++) {
sum2 += v5[i];
int now = i & 1, pre = now ^ 1;
for(int j = 0; j <= m; j++)
for(int k = 0; k <= sum2; k++)
dp[now][j][k] = dp[pre][j][k];
for(int j = 1; j <= i; j++)
for(int k = v5[i]; k <= sum2; k++)
if(dp[pre][j-1][k-v5[i]] >= 0)
dp[now][j][k] = max(dp[pre][j-1][k - v5[i]] + v2[i], dp[now][j][k]);
}
int ans = 0;
for(int i = 0; i < 1500; i++) {
if(dp[n&1][m][i] >= 0)
ans = max(ans, min(dp[n&1][m][i], i));
}
cout<<ans<<endl;
}
};
int main() {
// file();
Solver::solve();
return 0;
}
题目4 : 麻烦的第K大问题
数据出弱了…
枚举区间的起始位置,枚举区间的长度,然后用主席树查询区间第k大,总复杂度O(n*(n/len)*logn)。
就酱= =
#define others
#ifdef poj
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#endif // poj
#ifdef others
#include <bits/stdc++.h>
#endif // others
//#define file
#define all(x) x.begin(), x.end()
using namespace std;
const double pi = acos(-1.0);
typedef long long LL;
typedef unsigned long long ULL;
void umax(int &a, int b) {
a = max(a, b);
}
void umin(int &a, int b) {
a = min(a, b);
}
void file() {
freopen("a.in", "r", stdin);
// freopen("1.txt", "w", stdout);
}
namespace Solver {
const int maxn = 110000;
using namespace std;
struct seg{int l, r, sum;}tr[maxn*40];
int cnt, n, m, root[maxn], tmp, arr[maxn], v[maxn], k;
void update(int &x, int y, int l, int r, int pos, int val){//单点更新
int pre = x;//若带修改操作,则这里要查看是否有历史版本
x = ++cnt;tr[x] = pre? tr[pre] : tr[y];tr[x].sum += val;//每次更新重新建树链
if(l == r) return ;//更新到单点,更新完成
int m = (l+r)/2;//对所有包含pos的区间进行更新。
if(pos <= m) update(tr[x].l, tr[y].l, l, m, pos, val);
else update(tr[x].r, tr[y].r, m+1, r, pos, val);
}
int query(int x, int y, int l, int r, int k){
if(l == r) return l;//二分锁定第k大
int m = (l+r)/2;
int sum = tr[tr[y].l].sum - tr[tr[x].l].sum;//若两个版本的左子树上差值已经大于k,则第k大在左子树上
if(sum >= k) return query(tr[x].l, tr[y].l, l, m, k);
else return query(tr[x].r, tr[y].r, m+1, r, k-sum);
}
int len, y;
vector<int> G;
bool cmp(int a, int b) {
return a > b;
}
void solve() {
scanf("%d%d%d%d", &n, &len, &y, &k);
for(int i = 1; i <= n; i++) scanf("%d", &v[i]), update(root[i], root[i-1], 1, n, v[i], 1);
for(int i = 1; i <= n; i++)
for(int j = 1; i + j * len - 1<= n; j++) {
int d = i+j*len-1 - i + 1;
// cout<<d<<endl;
G.push_back(query(root[i-1], root[i+j*len-1], 1, n, d+1-j*y));
}
sort(all(G), cmp);
// for(auto v:G) cout<<v<<endl;
cout<<G[k-1]<<endl;
}
};
int main() {
// file();
Solver::solve();
return 0;
}