1004:Distribution of books
求最大值的最小值,明晃晃的告诉我们用二分。二分答案,然后考虑怎么check
设当前检查答案lim是否合法:
用dp来检验。
d
p
[
i
]
dp[i]
dp[i]表示[1,i]这个区间在满足每个小朋友的愉悦值不超过lim的情况下最多可以分给几个小朋友。那么转移为:
d
p
[
i
]
=
m
a
x
(
d
p
[
j
]
+
1
)
dp[i]=max(dp[j]+1)
dp[i]=max(dp[j]+1),
j
j
j需要满足的条件为:
s
u
m
[
i
]
−
s
u
m
[
j
]
<
=
l
i
m
sum[i]-sum[j]<=lim
sum[i]−sum[j]<=lim,即
s
u
m
[
j
]
>
=
s
u
m
[
i
]
−
l
i
m
sum[j]>=sum[i]-lim
sum[j]>=sum[i]−lim。sum数组为前缀和数组。
直接转移的复杂度是
n
2
n^2
n2的,所以考虑用线段树优化。
对于每个
d
p
[
i
]
dp[i]
dp[i],它是由满足
s
u
m
[
j
]
>
=
s
u
m
[
i
]
−
l
i
m
sum[j]>=sum[i]-lim
sum[j]>=sum[i]−lim的最大的
d
p
[
j
]
dp[j]
dp[j]转移来的。我们要找到这个满足条件的
d
p
[
j
]
dp[j]
dp[j]。
所以对于线段树上的点
x
x
x,维护一个使得
d
p
[
j
]
=
x
dp[j]=x
dp[j]=x的最大的
s
u
m
[
j
]
sum[j]
sum[j]。对
d
p
[
i
]
dp[i]
dp[i]转移的时候,只需要找到线段树上存储的值大于等于
s
u
m
[
i
]
−
l
i
m
sum[i]-lim
sum[i]−lim的最大下标即可。
ac代码:
#include<bits/stdc++.h>
#define ll long long
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 2e5 + 50;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int n, k;
ll a[maxn];
ll sum[maxn];
ll mx[maxn<<2];
void up(int rt){
mx[rt] = max(mx[rt<<1], mx[rt<<1|1]);
}
void build(int rt, int l, int r)
{
mx[rt] = -inf;
if(l == r) return;
build(lson);
build(rson);
}
void add(int rt, int l, int r, int i, ll val){
if(l == r){
mx[rt] = max(mx[rt], val);return;
}
if(i <= mid) add(lson, i, val);
else add(rson, i, val);
up(rt);
}
int query(int rt, int l, int r, ll val){//在l到r寻找一个最大的满足mx[i] >= val的i
if(mx[rt] < val) return -1;
if(l == r) return l;
int ans = query(rson, val);
if(ans == -1) ans = query(lson, val);
return ans;
}
void init()
{
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i) {
scanf("%lld", &a[i]);
sum[i] = sum[i-1] + a[i];
}
}
int dp[maxn];
bool check(ll lim)
{
build(1, 1, k);
for(int i = 1; i <= n; ++i) {
dp[i] = 0;
if(sum[i] <= lim) dp[i] = 1;
int t = query(1, 1, k, sum[i] - lim);
dp[i] = max(dp[i], t + 1);
if(dp[i] >= k) return true;
if(dp[i] > 0) add(1, 1, k, dp[i], sum[i]);
}
return false;
}
void sol()
{
ll t = 1e9 + 7;
ll l = -t*n, r = t*n;
ll ans;
while(l <= r){
if(check(mid)) {
ans = mid;
r = mid - 1;
}
else l = mid + 1;
}
printf("%lld\n", ans);
}
int main()
{
int T;cin>>T;
while(T--){
init();sol();
}
}
1006: Fansblog
由于素数之间的间隔不会太大,所以可以直接向下枚举找到相邻的素数(用Miller Rabbin判断是否素数)。
打表发现对于一个素数P,有(P-1)!%P = P-1,然后就反向挨个乘逆元求得答案了。(数学上来先打表
#include<iostream>
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
#include<time.h>
#define ll long long
using namespace std;
ll mul(ll a, ll b, ll m){
ll res = 0;
while(b){
if(b&1) res =(res + a)%m;
a = (a + a) % m;
b >>= 1;
}
return res;
}
ll qm(ll a, ll b, ll m){
ll res = 1;
a%=m;
while(b){
if(b&1) res = mul(res,a,m);
a = mul(a,a,m);
b>>=1;
}return res;
}
ll gcd(ll a, ll b){
if(b == 0) return a;
while(a%b){
ll t = a%b;
a = b; b = t;
}return b;
}
bool check(ll n, int repeat){
if(n < 2) return false;
if(n == 2 || n == 3) return true;
if(n%2 == 0) return false;
ll d = n-1;
int s = 0;
while(!(d&1)) s++, d>>=1;//x - 1 = 2^s * d;
//srand((unsigned)time(NULL));
while(repeat--){
ll a = (ll)rand()%(n-1) + 1;
ll x = qm(a,d,n);
ll y = 0;
for(int j = 0; j < s; ++j){
y = mul(x, x, n);
if(y == 1 && x != 1 && x != n-1) return false;
x = y;
}
if(y!=1) return false;
}
return true;
}
ll pollard_rho(ll n, int c){
// srand((unsigned)time(NULL));
ll x = rand()%(n-1) + 1;
ll y = x, i = 1, k = 2;
while(1){
i++;
x = (mul(x, x, n) + c)%n;
ll d = gcd((x - y + n)%n, n);
if(d > 1 && d < n) {
return d;
}
if(x == y) {
return n;
}
if(i == k) {
y = x; k <<= 1;
}
}
}
ll pri[500];
int cnt = 0;
void fnd(ll n, int c){
if(n == 1) return;
if(check(n, 20)){
pri[cnt++] = n;return;
}
int k = c;
ll p = n;
while(p == n) p = pollard_rho(n, c--);
fnd(p, k);
fnd(n/p, k);
}
bool is_prime(ll n){
return check(n, 20);
}
int main(){
int T;cin>>T;
while(T--){
ll P;
scanf("%lld", &P);
ll Q;
for(Q = P-2; Q > 0; Q-=2){
if(is_prime(Q)) break;
}
ll ans = 1;
for(ll i = P-2; i > Q; --i){
ll inv = qm(i, P-2, P);
ans = mul(ans, inv, P);
}
printf("%lld\n", ans);
}
return 0;
}
1007: Find the answer
问题等价于m-a[i]容量的背包最多放入a[1]…a[i-1]中几个数。贪心,从小到大放。直接暴力显然超时,离散化后线段树维护区间数字个数和区间数字和,左子树全可以放就全放然后搜右子树,否则递归左子树。注意行末要加空格
(头一次看到需要加行末空格的输出格式
#include<bits/stdc++.h>
#define ll long long
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 2e5 + 50;
int n;
ll m;
ll sum[maxn<<2], sz[maxn<<2];
ll w[maxn], cc[maxn];
int num;
void build(int rt, int l, int r)
{
sum[rt] = sz[rt] = 0;
if(l == r) return;
build(lson);
build(rson);
}
void add(int rt, int l, int r, int i){
if(l == r) {
sz[rt] ++;
sum[rt] = sum[rt] + cc[i];return;
}
if(i <= mid) add(lson, i);
else add(rson, i);
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
sz[rt] = sz[rt<<1] + sz[rt<<1|1];
return;
}
int query(int rt, int l, int r, ll &res){
if(sum[rt] <= res){
res -= sum[rt];
return sz[rt];
}
if(cc[l] > res) return 0;
if(l == r){
int ans = res / cc[l];
res = res - ans*cc[l];
return ans;
}
int ans = 0;
ans += query(lson, res);
ans += query(rson, res);
return ans;
}
void init(){
scanf("%d%lld", &n, &m);
num = 0;
for(int i = 1; i <= n; ++i){
scanf("%lld", &w[i]);
cc[++num] = w[i];
}
sort(cc+1,cc+1+num);
num = unique(cc+1, cc+1+num) - cc - 1;
build(1, 1, num);
}
int ans[maxn];
void sol(){
for(int i = 1; i <= n; ++i){
if(sum[1] <= m - w[i]) ans[i] = 0;
else{
ll res = m - w[i];
int t = query(1, 1, num, res);
//printf("%d", i-1-t);
ans[i] = i-1-t;
}
int pos = lower_bound(cc+1,cc+1+num,w[i]) - cc;
add(1, 1, num, pos);
}
for(int i = 1; i <= n; ++i){
printf("%d ", ans[i]);
}printf("\n");
}
int main()
{
int T;cin>>T;
while(T--){
init();sol();
}
}