2019杭电多校第三场

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 ] &lt; = l i m sum[i]-sum[j]&lt;=lim sum[i]sum[j]<=lim,即 s u m [ j ] &gt; = s u m [ i ] − l i m sum[j]&gt;=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 ] &gt; = s u m [ i ] − l i m sum[j]&gt;=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();
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值