CF 1539

CF1539

A:

分为两种情况:

  1. 相同数目。
  2. 递减的数目。

第一种情况,每次能有 \(\lfloor \frac{t}{x} \rfloor\) 个,第二段就是等差数列,直接每次 \(-1\) 加和就行。

直接计算即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll k,n,x,t,a,ans;
int main(){
	cin>>k;
	while(k--){
		cin>>n>>x>>t;
		a=min(n,t/x),ans=a*(n-a)+a*(a-1)/2,
		printf("%lld\n",ans);

    }
    return 0;
}

B:

一句话题意:输入一个字符串,求这个字符串几组区间内的总和

我们可以使用前缀和,用后一个位置减去前一个位置得到答案

#include<bits/stdc++.h>
using namespace std;
int n,q,l,r,ans[100005];
string song;
int main()
{
    cin>>n>>q>>song;
	for(int i=1;i<=n;++i) ans[i]=ans[i-1]+song[i-1]-'a'+1;
	for(int i=1;i<=q;++i){
		cin>>l>>r;
		cout<<ans[r]-ans[l-1]<<endl;
	}
    return 0;
}

C:

现在有 \(n\) 个学生,每个学生的水平为 \(a_i\),允许你往其中添加 \(k\) 个任意水平的学生。

现在要求你把这 \(n\) 个学生分成几组,使得每一组中的水平大小相邻的学生的水平绝对差小于等于 \(x\)

考虑贪心,排序后合并差值小的两个组一定是不劣的。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int n,k,x,ans;
int tot,a[N],com[N];
inline bool cmp(int x,int y) { return x>y; }
signed main()
{
	cin>>n>>k>>x;
	for(register int i=1;i<=n;i++) scanf("%lld",&a[i]);
	sort(a+1,a+n+1,cmp);
	for(register int i=1;i<n;i++) //不加学生,分组
		if(a[i]-a[i+1]>x) com[++tot]=a[i]-a[i+1];
	ans=tot+1;
	sort(com+1,com+tot+1);
	for(register int i=1;i<=tot;i++){
		int need=com[i]/x;
		if(com[i]%x==0) need-=1;
		if(need>k) break;
		else k-=need,ans-=1;
	}
	printf("%lld\n",ans);
	return 0;
}

D:

每一件商品对于我们来说是平等的。不同的只是商品的降价时间。所以,为了最小化代价,我们需要让更多的商品降价。

  1. 有降价商品,尽量买完。
  2. 没有降价,\(b_i\) 越小,降价越早。因此我们尽量买 \(b_i\) 大的,形成 \(1\) 情况。

很明显,我们可以维护两个碰撞指针。

从我们的策略可知,我们买东西只可能从两端买。

因此我们用两个指针标记没有买过的商品的左右两个端点。买完某种商品后将两个指针向中间靠拢。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define b second
#define a first
typedef long long ll;
typedef pair<ll,ll> pii;
ll n,ans;
pii o[N];
int main(){
    cin>>n;
    for(int i=0;i<n;i++) cin>>o[i].b>>o[i].a;
    sort(o,o+n);
    for(ll l=0,r=n-1,cnt=0,t;l<=r;o[r].b?0:r--)
        if(o[l].a>cnt)
        cnt+=t=min(o[l].a-cnt,o[r].b),ans+=2*t,o[r].b-=t;
        else cnt+=t=o[l++].b,ans+=t;
    cout<<ans<<endl;
    // system("pause");
    return 0;
}

E:

题意:经过 \(n\) 次替换操作,需要满足两个数在给定的范围内,并求出过程。

考虑 \(dp\)

假如卡片给 \(x\) ,那么 \(y\) 下一轮不变, \(y\) 还需要满足第 \(i+1\) 个限制.
从这里容易想到每张卡片的持续效果一定是一段区间.

预处理 \(lx[i]\) 表示第 \(i\) 轮卡片给 \(x\),能向后扩展的最大位置, \(ly\) 同理。

然后设计状态为 \(dp[i][0/1]\),
\(dp[i][0]\) 表示当前卡片给 \(0\) 时,\(1\) 能扩展的最大位置.
由于知道此时卡片给 \(0\) 了,所以 \(0\) 的扩展长度也能知道,即 \(lx[i]\).
这样的状态直接就能够表示两个数的扩展位置了.

剩下的就是 \(O(n)\)\(dp\) 了,转移比较简单.

未解决的问题为:如何预处理 \(lx[]\)\(ly[]\).
\(st\) 表预处理 \(f[i][j]=\) 从区间 \(i\) 开始,向后走 \(2^j-1\)步,能得到的区间并.
对于每个位置 \(i\),二分其向后扩展的长度,用 \(st\)\(check\) 能否扩展到即可.

#include<bits/stdc++.h>
#define PI pair<int,int>
using namespace std;
const int N=1e5+5,M=20;
int la[N],ra[N];
int lb[N],rb[N];
PI f[N][25];
int pos[2][N];
int pre[N][2];
int d[N][2];
int val[N];
int lg2[N];
int n,m;
PI merged(PI a,PI b){
    if(a.first==-1)return b;
    if(b.first==-1)return a;
    if(a.first==-2)return {-2,-2};
    if(b.first==-2)return {-2,-2};
    PI ans={max(a.first,b.first),min(a.second,b.second)};
    if(ans.first>ans.second)ans={-2,-2};//无交集
    return ans;
}
void init(){
    lg2[1]=0;
    for(int i=2;i<N;i++){
        lg2[i]=lg2[i-1];
        if((i&(i-1))==0)lg2[i]++;
    }
}
PI ask(int l,int r){
    int k=lg2[r-l+1];
    return merged(f[l][k],f[r-(1<<k)+1][k]);
}
void build(int* l,int* r,int* pos){
    for(int i=0;i<N;i++){
        for(int j=0;j<25;j++){
            f[i][j]={-1,-1};
        }
    }
    for(int i=1;i<=n;i++){
        f[i][0]={l[i],r[i]};
    }
    for(int j=1;j<=M;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            f[i][j]=merged(f[i][j-1],f[i+(1<<(j-1))][j-1]);
        }
    }
    for(int i=1;i<=n;i++){
        int ans=0;
        int l=1,r=n-i+1;
        while(l<=r){
            int mid=(l+r)/2;
            PI x=ask(i,i+mid-1);
            if(x.first<=val[i]&&x.second>=val[i]){
                ans=mid,l=mid+1;
            }else{
                r=mid-1;
            }
        }
        pos[i]=i+ans-1;
    }
}
void dfs(int i,int j){
    if(i!=1){
        dfs(i-1,pre[i][j]);
    }
    printf("%d ",j);
}
void solve(){
    init();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d%d%d%d",&val[i],&la[i],&ra[i],&lb[i],&rb[i]);
    build(la,ra,pos[0]);
    build(lb,rb,pos[1]);
    PI last={0,1e9};
    for(int i=1;i<=n;i++){
        last=merged(last,{la[i],ra[i]});
        if(last.first<=0&&last.second>=0){
            d[1][1]=i;
        }else break;
    }
    last={0,1e9};
    for(int i=1;i<=n;i++){
        last=merged(last,{lb[i],rb[i]});
        if(last.first<=0&&last.second>=0){
            d[1][0]=i;
        }else break;
    }
    for(int i=2;i<=n;i++){
        for(int j=0;j<2;j++){//枚举当前状态
            for(int last=0;last<2;last++){//枚举上一张卡片的位置
                int p1=pos[last][i-1];
                int p2=d[i-1][last];
                if(j==last){
                    if(p1<i-1)continue;
                    if(p2>=d[i][j]){
                        d[i][j]=p2;
                        pre[i][j]=last;
                    }
                }else{//j!=last
                    if(p2<i-1)continue;
                    if(p1>=d[i][j]){
                        d[i][j]=p1;
                        pre[i][j]=last;
                    }
                }
            }
        }
    }
    for(int j=0;j<2;j++){
        int p1=pos[j][n];
        int p2=d[n][j];
        if(p1>=n&&p2>=n){
            puts("YES");
            dfs(n,j);
            return ;
        }
    }
    puts("NO");
}
signed main(){
    solve();
    return 0;
}

F:

目前还不太会做,但有些思路:

如果该数在区间中心的右边,那么它离中心的距离应该是区间内小于等于它的个数减去大于等于它的个数再除以二,

数在左边同样可以得到一个式子。

因此我们处理一下,通过线段树维护最大前缀和后缀就可以。

#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, ans[N], x;
struct node {
    int sum, pre, suf;
    node(int x = 0, int y = 0, int z = 0) { sum = x, pre = y, suf = z; }
    friend node operator+(node a, node b) {
        return node(a.sum + b.sum, std::max(a.pre, b.pre + a.sum),
                   std::max(b.suf, a.suf + b.sum));
    }
} t[N << 2];
std::vector<int> pos[N];
void Update(int p, int l, int r, int x, int y) {
    if (l == r) {
        t[p].pre = std::max(0, y), t[p].suf = std::max(0, y);
        t[p].sum = y;
        return;
    }
    int mid = l + (r - l) / 2;
    if (x <= mid)
        Update(p + p, l, mid, x, y);
    else
        Update(p + p + 1, mid + 1, r, x, y);
    t[p] = t[p + p] + t[p + p + 1];
}
node Query(int p, int l, int r, int x, int y) {
    if (x > y) return node(0, 0, 0);
    if (l == x && r == y) return t[p];
    int mid = l + (r - l) / 2;
    if (y <= mid)
        return Query(p + p, l, mid, x, y);
    else if (x > mid)
        return Query(p + p + 1, mid + 1, r, x, y);
    else
        return Query(p + p, l, mid, x, mid) +
               Query(p + p + 1, mid + 1, r, mid + 1, y);
}
void solve1() {
    for (int i = 1; i <= n; i++) Update(1, 1, n, i, 1);
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < (signed)pos[i].size(); j++) {
            int v = pos[i][j];
            int an =
                Query(1, 1, n, 1, v - 1).suf + 1 + Query(1, 1, n, v + 1, n).pre;
            ans[v] = std::max(ans[v], an / 2);
        }
        for (int j = 0; j < (signed)pos[i].size(); j++)
            Update(1, 1, n, pos[i][j], -1);
    }
}
void solve2() {
    for (int i = 1; i <= n; i++) Update(1, 1, n, i, 1);
    for (int i = n; i >= 1; i--) {
        for (int j = 0; j < (signed)pos[i].size(); j++) {
            int v = pos[i][j];
            int an =
                Query(1, 1, n, 1, v - 1).suf + 1 + Query(1, 1, n, v + 1, n).pre;
            ans[v] = std::max(ans[v], (an - 1) / 2);
        }
        for (int j = 0; j < (signed)pos[i].size(); j++)
            Update(1, 1, n, pos[i][j], -1);
    }
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &x), pos[x].push_back(i);
    solve1(), solve2();
    for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
    return 0;
}
```## CF1539


### A:

分为两种情况:
1. 相同数目。
2. 递减的数目。
   
第一种情况,每次能有 $\lfloor \frac{t}{x} \rfloor$ 个,第二段就是等差数列,直接每次 $-1$ 加和就行。

直接计算即可。
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll k,n,x,t,a,ans;
int main(){
	cin>>k;
	while(k--){
		cin>>n>>x>>t;
		a=min(n,t/x),ans=a*(n-a)+a*(a-1)/2,
		printf("%lld\n",ans);

    }
    return 0;
}

B:

一句话题意:输入一个字符串,求这个字符串几组区间内的总和

我们可以使用前缀和,用后一个位置减去前一个位置得到答案

#include<bits/stdc++.h>
using namespace std;
int n,q,l,r,ans[100005];
string song;
int main()
{
    cin>>n>>q>>song;
	for(int i=1;i<=n;++i) ans[i]=ans[i-1]+song[i-1]-'a'+1;
	for(int i=1;i<=q;++i){
		cin>>l>>r;
		cout<<ans[r]-ans[l-1]<<endl;
	}
    return 0;
}

C:

现在有 \(n\) 个学生,每个学生的水平为 \(a_i\),允许你往其中添加 \(k\) 个任意水平的学生。

现在要求你把这 \(n\) 个学生分成几组,使得每一组中的水平大小相邻的学生的水平绝对差小于等于 \(x\)

考虑贪心,排序后合并差值小的两个组一定是不劣的。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int n,k,x,ans;
int tot,a[N],com[N];
inline bool cmp(int x,int y) { return x>y; }
signed main()
{
	cin>>n>>k>>x;
	for(register int i=1;i<=n;i++) scanf("%lld",&a[i]);
	sort(a+1,a+n+1,cmp);
	for(register int i=1;i<n;i++) //不加学生,分组
		if(a[i]-a[i+1]>x) com[++tot]=a[i]-a[i+1];
	ans=tot+1;
	sort(com+1,com+tot+1);
	for(register int i=1;i<=tot;i++){
		int need=com[i]/x;
		if(com[i]%x==0) need-=1;
		if(need>k) break;
		else k-=need,ans-=1;
	}
	printf("%lld\n",ans);
	return 0;
}

D:

每一件商品对于我们来说是平等的。不同的只是商品的降价时间。所以,为了最小化代价,我们需要让更多的商品降价。

  1. 有降价商品,尽量买完。
  2. 没有降价,\(b_i\) 越小,降价越早。因此我们尽量买 \(b_i\) 大的,形成 \(1\) 情况。

很明显,我们可以维护两个碰撞指针。

从我们的策略可知,我们买东西只可能从两端买。

因此我们用两个指针标记没有买过的商品的左右两个端点。买完某种商品后将两个指针向中间靠拢。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define b second
#define a first
typedef long long ll;
typedef pair<ll,ll> pii;
ll n,ans;
pii o[N];
int main(){
    cin>>n;
    for(int i=0;i<n;i++) cin>>o[i].b>>o[i].a;
    sort(o,o+n);
    for(ll l=0,r=n-1,cnt=0,t;l<=r;o[r].b?0:r--)
        if(o[l].a>cnt)
        cnt+=t=min(o[l].a-cnt,o[r].b),ans+=2*t,o[r].b-=t;
        else cnt+=t=o[l++].b,ans+=t;
    cout<<ans<<endl;
    // system("pause");
    return 0;
}

E:

题意:经过 \(n\) 次替换操作,需要满足两个数在给定的范围内,并求出过程。

考虑 \(dp\)

假如卡片给 \(x\) ,那么 \(y\) 下一轮不变, \(y\) 还需要满足第 \(i+1\) 个限制.
从这里容易想到每张卡片的持续效果一定是一段区间.

预处理 \(lx[i]\) 表示第 \(i\) 轮卡片给 \(x\),能向后扩展的最大位置, \(ly\) 同理。

然后设计状态为 \(dp[i][0/1]\),
\(dp[i][0]\) 表示当前卡片给 \(0\) 时,\(1\) 能扩展的最大位置.
由于知道此时卡片给 \(0\) 了,所以 \(0\) 的扩展长度也能知道,即 \(lx[i]\).
这样的状态直接就能够表示两个数的扩展位置了.

剩下的就是 \(O(n)\)\(dp\) 了,转移比较简单.

未解决的问题为:如何预处理 \(lx[]\)\(ly[]\).
\(st\) 表预处理 \(f[i][j]=\) 从区间 \(i\) 开始,向后走 \(2^j-1\)步,能得到的区间并.
对于每个位置 \(i\),二分其向后扩展的长度,用 \(st\)\(check\) 能否扩展到即可.

#include<bits/stdc++.h>
#define PI pair<int,int>
using namespace std;
const int N=1e5+5,M=20;
int la[N],ra[N];
int lb[N],rb[N];
PI f[N][25];
int pos[2][N];
int pre[N][2];
int d[N][2];
int val[N];
int lg2[N];
int n,m;
PI merged(PI a,PI b){
    if(a.first==-1)return b;
    if(b.first==-1)return a;
    if(a.first==-2)return {-2,-2};
    if(b.first==-2)return {-2,-2};
    PI ans={max(a.first,b.first),min(a.second,b.second)};
    if(ans.first>ans.second)ans={-2,-2};//无交集
    return ans;
}
void init(){
    lg2[1]=0;
    for(int i=2;i<N;i++){
        lg2[i]=lg2[i-1];
        if((i&(i-1))==0)lg2[i]++;
    }
}
PI ask(int l,int r){
    int k=lg2[r-l+1];
    return merged(f[l][k],f[r-(1<<k)+1][k]);
}
void build(int* l,int* r,int* pos){
    for(int i=0;i<N;i++){
        for(int j=0;j<25;j++){
            f[i][j]={-1,-1};
        }
    }
    for(int i=1;i<=n;i++){
        f[i][0]={l[i],r[i]};
    }
    for(int j=1;j<=M;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            f[i][j]=merged(f[i][j-1],f[i+(1<<(j-1))][j-1]);
        }
    }
    for(int i=1;i<=n;i++){
        int ans=0;
        int l=1,r=n-i+1;
        while(l<=r){
            int mid=(l+r)/2;
            PI x=ask(i,i+mid-1);
            if(x.first<=val[i]&&x.second>=val[i]){
                ans=mid,l=mid+1;
            }else{
                r=mid-1;
            }
        }
        pos[i]=i+ans-1;
    }
}
void dfs(int i,int j){
    if(i!=1){
        dfs(i-1,pre[i][j]);
    }
    printf("%d ",j);
}
void solve(){
    init();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d%d%d%d",&val[i],&la[i],&ra[i],&lb[i],&rb[i]);
    build(la,ra,pos[0]);
    build(lb,rb,pos[1]);
    PI last={0,1e9};
    for(int i=1;i<=n;i++){
        last=merged(last,{la[i],ra[i]});
        if(last.first<=0&&last.second>=0){
            d[1][1]=i;
        }else break;
    }
    last={0,1e9};
    for(int i=1;i<=n;i++){
        last=merged(last,{lb[i],rb[i]});
        if(last.first<=0&&last.second>=0){
            d[1][0]=i;
        }else break;
    }
    for(int i=2;i<=n;i++){
        for(int j=0;j<2;j++){//枚举当前状态
            for(int last=0;last<2;last++){//枚举上一张卡片的位置
                int p1=pos[last][i-1];
                int p2=d[i-1][last];
                if(j==last){
                    if(p1<i-1)continue;
                    if(p2>=d[i][j]){
                        d[i][j]=p2;
                        pre[i][j]=last;
                    }
                }else{//j!=last
                    if(p2<i-1)continue;
                    if(p1>=d[i][j]){
                        d[i][j]=p1;
                        pre[i][j]=last;
                    }
                }
            }
        }
    }
    for(int j=0;j<2;j++){
        int p1=pos[j][n];
        int p2=d[n][j];
        if(p1>=n&&p2>=n){
            puts("YES");
            dfs(n,j);
            return ;
        }
    }
    puts("NO");
}
signed main(){
    solve();
    return 0;
}

F:

目前还不太会做,但有些思路:

如果该数在区间中心的右边,那么它离中心的距离应该是区间内小于等于它的个数减去大于等于它的个数再除以二,

数在左边同样可以得到一个式子。

因此我们处理一下,通过线段树维护最大前缀和后缀就可以。

#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, ans[N], x;
struct node {
    int sum, pre, suf;
    node(int x = 0, int y = 0, int z = 0) { sum = x, pre = y, suf = z; }
    friend node operator+(node a, node b) {
        return node(a.sum + b.sum, std::max(a.pre, b.pre + a.sum),
                   std::max(b.suf, a.suf + b.sum));
    }
} t[N << 2];
std::vector<int> pos[N];
void Update(int p, int l, int r, int x, int y) {
    if (l == r) {
        t[p].pre = std::max(0, y), t[p].suf = std::max(0, y);
        t[p].sum = y;
        return;
    }
    int mid = l + (r - l) / 2;
    if (x <= mid)
        Update(p + p, l, mid, x, y);
    else
        Update(p + p + 1, mid + 1, r, x, y);
    t[p] = t[p + p] + t[p + p + 1];
}
node Query(int p, int l, int r, int x, int y) {
    if (x > y) return node(0, 0, 0);
    if (l == x && r == y) return t[p];
    int mid = l + (r - l) / 2;
    if (y <= mid)
        return Query(p + p, l, mid, x, y);
    else if (x > mid)
        return Query(p + p + 1, mid + 1, r, x, y);
    else
        return Query(p + p, l, mid, x, mid) +
               Query(p + p + 1, mid + 1, r, mid + 1, y);
}
void solve1() {
    for (int i = 1; i <= n; i++) Update(1, 1, n, i, 1);
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < (signed)pos[i].size(); j++) {
            int v = pos[i][j];
            int an =
                Query(1, 1, n, 1, v - 1).suf + 1 + Query(1, 1, n, v + 1, n).pre;
            ans[v] = std::max(ans[v], an / 2);
        }
        for (int j = 0; j < (signed)pos[i].size(); j++)
            Update(1, 1, n, pos[i][j], -1);
    }
}
void solve2() {
    for (int i = 1; i <= n; i++) Update(1, 1, n, i, 1);
    for (int i = n; i >= 1; i--) {
        for (int j = 0; j < (signed)pos[i].size(); j++) {
            int v = pos[i][j];
            int an =
                Query(1, 1, n, 1, v - 1).suf + 1 + Query(1, 1, n, v + 1, n).pre;
            ans[v] = std::max(ans[v], (an - 1) / 2);
        }
        for (int j = 0; j < (signed)pos[i].size(); j++)
            Update(1, 1, n, pos[i][j], -1);
    }
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &x), pos[x].push_back(i);
    solve1(), solve2();
    for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
    return 0;
}
posted @ 2021-08-30 19:27  Evitagen  阅读(27)  评论(0编辑  收藏  举报