CF1904 Codeforces Round 914 (Div. 2) VP题解

A

题目大意:

无限大棋盘上给定两个棋子的坐标,问有多少个位置能够使得能走 ( a , b ) (a,b) (a,b) 格的马捉双

思路:

用map存,模拟即可,学到了make_pair(a,b)和{a,b}等价

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
map<pii,int> mp;
int dx[]={1,-1,1,-1};
int dy[]={-1,1,1,-1};
void work()
{
    int a,b,qx,qy,kx,ky;
    cin>>a>>b;
    cin>>kx>>ky>>qx>>qy;
    for(int i=0;i<4;i++)
    {
        mp[make_pair(kx+a*dx[i],ky+b*dy[i])]++;
        if(a!=b) mp[make_pair(kx+b*dx[i],ky+a*dy[i])]++;
        mp[make_pair(qx+a*dx[i],qy+b*dy[i])]++;
        if(a!=b) mp[make_pair(qx+b*dx[i],qy+a*dy[i])]++;
    }
    int ans=0;
    trav(item,mp)
        if(item.second>1) ans++;
    cout<<ans<<'\n';
    mp.clear();
}
int main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

B

题目大意:

给定一个数组 a a a,有以下规则

如果当前分数大于或等于 a i a_i ai,那么你可以将当前分数增加 a i a_i ai,并将 a i a_i ai从数组中移除

一开始可以任意选择一个 a i a_i ai移除并作为初始点数

对于每个 i i i,输出以 a i a_i ai开始可以移除的最多数的个数

1 ≤ n ≤ 1 e 5 1 \le n \le 1e5 1n1e5

思路:

排序,如果大数被移除,那么小数一定可以被移除

不难想到设前缀和为 s i s_i si,显然当 s i − 1 > = a i s_{i-1}>=a_i si1>=ai时这些数都可以移除

那么对于每个 i i i而言,只需要找到最小的 i < = j i<=j i<=j且满足 s j − 1 < a [ j ] s_{j-1}<a[j] sj1<a[j] j j j就是答案了

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;

const int maxn=1e5+5;
map<int,int> f;
int n;
int a[maxn],b[maxn];
int ans[maxn];
ll s[maxn];

void clean()
{
    f.clear();
}
void work()
{

    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i];
    sort(a+1,a+n+1);
    for(int i=1;i<=n;i++) f[a[i]]=i,s[i]=s[i-1]+a[i];
    ans[n]=n;
    for(int i=n-1;i>=1;i--)
        if(s[i]>=a[i+1]) ans[i]=ans[i+1];
        else ans[i]=i;
    for(int i=1;i<=n;i++)
        cout<<ans[f[b[i]]]-1<<' ';
    cout<<'\n';
    clean();
}
int main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

C

题目大意:

给定一个数组 a a a,每次从中选两个数 a i , a j , i ≠ j a_i,a_j,i \ne j ai,aj,i=j,将 ∣ a i − a j ∣ |a_i-a_j| aiaj加入到 a a a数组中

求恰好 k k k次这样的操作后, a a a数组的最小值

1 ≤ n ≤ 5000 1 \le n \le 5000 1n5000

思路:

构造题熟练的同学应该很容易观察到 k ≥ 3 k \ge 3 k3时显然答案为 0 0 0 k = 1 和 k = 2 k=1和k=2 k=1k=2的情况暴力做就可以了

需要注意的点

官方文档中的lower_bound和upperbound,需要保证容器有序

upper_bound()函数

1、返回指向范围[first,last)中第一个元素使得value < element(或comp(value,element))为true(即严格大的迭代器),如果找不到这样的元素,则为last。

2、[first,last)必须根据表达式!(value < element)或!comp(value,element)进行分区,即表达式为true的所有元素必须在表达式为false的所有元素之前!完全排序的[first,last)符合此条件

3、如果没有自定义比较函数就使用operator<来比较element,如果自定义了比较函数就使用comp来比较element

lower_bound()函数

1、返回指向范围[first,last)中的第一个元素使得该元素不满足element< value(或comp(element,value)为false)、(即大于或等于)的迭代器,如果找不到这样的元素,则返回last
2、[first,last)必须相对于表达式element< value(或comp(element,value))进行分区,即表达式为true的所有元素必须在表达式为false的所有元素之前。完全排序的[first,last)符合此条件

3、如果没有自定义比较函数就使用operator<来比较element,如果自定义了比较函数就使用comp来比较element

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;
const ll inf=1e18+5;
const int maxn=2e5+5;

int n,k;
ll a[maxn];
void work()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    if(k>=3)
    {
        cout<<"0\n";
        return;
    }
    sort(a+1,a+n+1);
    if(k==1)
    {
        ll ans=inf;
        for(int i=1;i<=n;i++) ans=min(ans,a[i]);
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
                ans=min(ans,abs(a[i]-a[j]));
        cout<<ans<<'\n';return;
    }
    else
    {
        ll ans=inf;
        for(int i=1;i<=n;i++) ans=min(ans,a[i]);
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
            {
                ans=min(ans,abs(a[i]-a[j]));
                int xl=lower_bound(a+1,a+n+1,abs(a[i]-a[j]))-a-1;
                ans=min(ans,abs(a[xl]-abs(a[i]-a[j])));
                if(xl!=n) ans=min(ans,abs(a[xl+1]-abs(a[i]-a[j])));
            }
        cout<<ans<<'\n';
    }
}
int main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

D

题目大意:

给定两个序列 a , b a,b a,b,每次可以选择一段区间 [ l , r ] [l,r] [l,r],将 a l 到 a r a_l到a_r alar赋值为这段区间的最大值

问是否存在操作序能使得 a a a序列变为 b b b序列,输出 Y E S / N O YES/NO YES/NO

1 ≤ n ≤ 2 e 5 1 \le n \le 2e5 1n2e5

思路:

对于某个 i i i,有如下情况

1、如果 a i > b i a_i>b_i ai>bi显然不可能

2、如果 a i = b i a_i=b_i ai=bi,可能会影响到其他值

3、如果 a i < b i a_i<b_i ai<bi,考虑 a i a_i ai是利用左边的还是右边的数变成 b i b_i bi的,假设从左边而来

这要求左边必须有一个 x x x使得 a x = b i a_x=b_i ax=bi,并且有

(1) [ x , i ] [x,i] [x,i]区间中 b b b都大于等于 b i b_i bi(不影响左边已经设置好的2、)

(2) [ x , i ] [x,i] [x,i]区间中 a a a都小于等于 b i b_i bi(能够设置成功)

显然离 i i i更近的 x x x一定不会比其他 x x x更劣,所以只要考虑上一次出现就可以了

左边扫一次,右边扫一次记录下第 i i i个位置可不可行就好了

(1)(2)两个限制可以用 S T ST ST表或者线段树简单维护,这里我写的是线段树

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define trav(a,x) for(auto&a : x)
#define all(x) x.begin(),x.end()
#define sz(x) (int) x.size()
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;

#define lson t<<1,l,mid
#define rson t<<1|1,mid+1,r
#define ls t<<1
#define rs t<<1|1

const int inf=1e7+5;
const int maxn=2e5+5;
struct Tree
{
    int l,r;
    int minn,maxx;
}tr[maxn<<2];
int a[maxn],b[maxn];
void pushup(int t)
{
    tr[t].maxx=max(tr[ls].maxx,tr[rs].maxx);
    tr[t].minn=min(tr[ls].minn,tr[rs].minn);
}
void build(int t,int l,int r)
{
    tr[t].l=l;tr[t].r=r;
    if(l==r)
    {
        tr[t].maxx=a[l];
        tr[t].minn=b[l];
        return;
    }
    int mid=(l+r)>>1;
    build(lson);build(rson);
    pushup(t);
}
int Querymax(int t,int ll,int rr)
{
    if(ll<=tr[t].l&&tr[t].r<=rr) return tr[t].maxx;
    int mid=(tr[t].l+tr[t].r)>>1;
    int res=-inf;
    if(ll<=mid) res=max(res,Querymax(ls,ll,rr));
    if(rr>mid)  res=max(res,Querymax(rs,ll,rr));
    return res;
}
int Querymin(int t,int ll,int rr)
{
    if(ll<=tr[t].l&&tr[t].r<=rr) return tr[t].minn;
    int mid=(tr[t].l+tr[t].r)>>1;
    int res=inf;
    if(ll<=mid) res=min(res,Querymin(ls,ll,rr));
    if(rr>mid)  res=min(res,Querymin(rs,ll,rr));
    return res;
}

int n;
int las[maxn],nxt[maxn],ans[maxn];
void clean()
{
    for(int i=1;i<=n;i++) las[i]=0,nxt[i]=0,ans[i]=false;
}
void work()
{
    cin>>n;
    rep(i,1,n) cin>>a[i];
    rep(i,1,n) cin>>b[i];
    build(1,1,n);
    //cout<<Querymax(1,1,5)<<"haha\n";
    rep(i,1,n)
    {
        if(a[i]==b[i]) ans[i]=true;
        if(!las[b[i]]) {las[a[i]]=i;continue;}
        if(Querymin(1,las[b[i]],i)>=b[i]&&Querymax(1,las[b[i]],i)<=b[i]) ans[i]=true;
        las[a[i]]=i;
    }
    for(int i=n;i>=1;i--)
    {
        if(!nxt[b[i]]) {nxt[a[i]]=i;continue;}
        if(Querymin(1,i,nxt[b[i]])>=b[i]&&Querymax(1,i,nxt[b[i]])<=b[i]) ans[i]=true;
        nxt[a[i]]=i;
    }
    rep(i,1,n)
        if(!ans[i]) {cout<<"NO\n";clean();return;}
    cout<<"YES\n";
    clean();
}
int main()
{
	cin.tie(0);
	cin.sync_with_stdio(0);
	int t=1;
	cin>>t;
	while(t--)
		work();
	return 0;
}

E(可补)

题目大意:

给定一棵树, q q q个询问

每次询问给定 x , k x,k x,k以及 k k k个点,问删除这 k k k个点以后从 x x x出发的最长简单路径(每次询问后会恢复到一开始的状态)

思路:

很典的DFS序+线段树,但已经一万年没写过数据结构了,之后补,还有虚树的解法我还没有看

F(可补)

题目大意:

给定一棵 n n n 个点的树与 m m m 个限制,每个限制形如 “点 c c c 的权值在 a a a b b b 的路径上最大/小”。试为每个点赋 1 ∼ n 1 \sim n 1n 中互不相同的权值,满足所有限制,或判断不存在。 n , m ≤ 2 × 1 0 5 n, m \le 2 \times 10^5 n,m2×105

思路:

赛时:对于这种”权值 i i i大于/小于权值 j j j”的若干条限制,一看就是建图嘛

但是边的数量爆炸了。当时没想到线段树优化建图,想到了也写不出~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值