AtCoder 刷题记 一

感觉思维不行,就打算在atcoder上刷刷题

 

目录

abc115D Christmas(分治)

arc086D Non-decreasing(思维)

abc091D Two Sequences(位运算+二分+思维)

arc076F Exhausted?(霍尔定理+线段树)

arc076E Connected?(思维+栈模拟)


abc115D Christmas(分治)

题意:一重的汉堡是abbba,两重的汉堡是a abbba b abbba a,相当于长度是*2+1,输入n和x,n为几重汉堡,x为从上到下吃了几层,一层就是一个字符,问一个吃了几个字符b。

思路:以为每一重汉堡的结构类似,并且前后都为a,我们可以先预处理出每一重汉堡的长度和b字符的个数,以a为边界进行移动,不断二分,同时累积答案,和线段树类似。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll x,n;
ll len[100],gs[100];
ll dfs(ll l,ll r,int x,ll sum,ll pos){//pos的记录开始的位置
    if(sum==0)return 0;
    if(x==1){
        return max(0ll,min(3ll,sum+pos-1-l));//只剩一重汉堡
    }
    ll ans=0;
    ll mid=(l+r)/2;
    if(sum+pos-1<mid)ans+=dfs(l+1,mid-1,x-1,sum-1,l+1);//前一半
    else if(sum+pos-1==mid)ans+=gs[x-1]+1;//刚好一半
    else{
        ans+=gs[x-1]+1+dfs(mid+1,r-1,x-1,sum-len[x]/2-1,mid+1);//后一半,要记得加上前面的个数
    }
    return ans;
}
int main()
{
    cin>>n>>x;
    gs[1]=3,len[1]=5;
    for(int i=2;i<=n;i++)gs[i]=gs[i-1]*2+1,len[i]=len[i-1]*2+3;//预处理
    printf("%lld\n",dfs(1,len[n],n,x,1));
}

arc086D Non-decreasing(思维)

题意:

给定一个n个元素的序列,每次操作有一个x和y,表示a_y+=a_x,问如何有用0到2*n次操作使得序列变成不严格单调递增的序列

思路

进行分类讨论,首先如果序列是大于等于0的,那么我们从前往后加,相当于前缀和,那么必然满足题意,需要n-1次操作。

如果序列是小于等于0的,那么我们从后往前加,相当于后缀和,同理n-1次操作。

最后就是有正有负,那么我们想怎么转化为前面的情况,就是找到序列中最小和最大的元素,比较他们的绝对值,用绝对值大的数和所有数加,那么就会变成情况一,或者情况二,总要2*n-1次操作,满足题目要求,十分的巧妙,一开始被n=50迷惑了。

代码:

#include<bits/stdc++.h>
using namespace std;
int n;
int arr[100];
int main()
{
    cin>>n;int mi=1e7,pmi,mx=-1e7,pmx;
    for(int i=1;i<=n;i++)cin>>arr[i],mi=min(mi,arr[i]),mx=max(mx,arr[i]);
    for(int i=1;i<=n;i++) {
        if(arr[i]==mi)pmi=i;
        if(arr[i]==mx)pmx=i;
    }
    if(mi>=0) {//都大于等于0
        cout<<n-1<<endl;
        for(int i=1;i<n;i++)cout<<i<<" "<<i+1<<endl;
    } else if(mx<=0) {//都小于等于
        cout<<n-1<<endl;
        for(int i=n;i>1;i--)cout<<i<<" "<<i-1<<endl;
    } else {//有正有负
        cout<<2*n-1<<endl;
        if(abs(mi)>abs(mx)) {//转化为第二种
            for(int i=1;i<=n;i++)cout<<pmi<<" "<<i<<endl;
            for(int i=n;i>1;i--)cout<<i<<" "<<i-1<<endl;
        } else {//转化为第一种
            for(int i=1;i<=n;i++)cout<<pmx<<" "<<i<<endl;
            for(int i=1;i<n;i++)cout<<i<<" "<<i+1<<endl;
        }
    }
}

abc091D Two Sequences(位运算+二分+思维)

题意:

给定两个n个元素的序列a,b,对于任意的1<=i,j<=n,有一个c=a_i+b_j,求这n^2个c的异或和。

思路

首先由于这n有1e5,不能够暴力,那么既然分别考虑答案的每一位,对于第k位,我们只要将a,b中的数字模上(1<<(k+1)),因为只有后面的k位会对第k位有影响,我们将b数组排序,对于每一个ai进行统计,首先a_i+b_j<2^{(k+2)},那么如果a_i+b_j<2^k就没有贡献,而2^{k+1}<a_i+b_j<2^{k+1}+2^{k}也没有贡献,用二分来统计一下即可。通过个数的奇偶更新答案。

总的时间复杂度为O(29*n*logn)

代码:

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
inline ll read()
{
    ll tmp=0; char c=getchar(),f=1;
    for(;c<'0'||'9'<c;c=getchar())if(c=='-')f=-1;
    for(;'0'<=c&&c<='9';c=getchar())tmp=(tmp<<3)+(tmp<<1)+c-'0';
    return tmp*f;
}
using namespace std;
ll a[200010],b[200010];
ll tmp[200010];
int n;
ll find(ll k)
{
    int l=1,r=n+1;
    while(l<r){
        int mid=(l+r)>>1;
        if(tmp[mid]>=k)r=mid;else l=mid+1;
    }
    return l;
}
int main()
{
    int i,k;
    n=read();
    for(i=1;i<=n;i++)a[i]=read();
    for(i=1;i<=n;i++)b[i]=read();
    ll ans=0;
    for(k=1;k<=29;k++){
        ll base=(1<<k)-1;
        for(i=1;i<=n;i++)tmp[i]=b[i]&base;
        sort(tmp+1,tmp+n+1);
        ll cnt=0;
        for(i=1;i<=n;i++)
            cnt+=n-find((1<<(k-1))-(a[i]&base))+1;
        for(i=1;i<=n;i++)
            cnt-=find((1<<k)+(1<<(k-1))-(a[i]&base))-find((1<<k)-(a[i]&base));
        if(cnt&1)ans+=(1<<(k-1));
    }
    printf("%lld\n",ans);
}

arc076F Exhausted?(霍尔定理+线段树)

题意:

给定n和m,表示有m个椅子编号1-m,n个人,每一个人有一个(l,r),表示,第i个人只能做[1,L_i]\bigcup[R_i,m]的椅子,问要是每个人都能坐下,最多添加多少个椅子。

思路

首先这是我们只要求出最多能坐X个人,把n-X就是答案了,一个二分图最大匹配的问题,但是按照朴素的做法,必定是TLE。

我们由霍尔定理得到

一个二分图存在完美匹配(每个人都可以坐在椅子上),要满足|X|<=\omega (X)|X|是左边的任意子集的大小,\omega (X)是在X的子集中都可以匹配的右边集合的大小。

那么我们可以知道,当max{ |X|-\omega(X) }=0的时候存在完美匹配,推广一下,max{ |X|-\omega(X) }就是要添加的椅子数,即答案。但是|X|是任意子集,枚举必然超时,我们考虑\omega (X)的形式发现其必然是[1,L] \& [R,m] ,我们可以枚举(L,R),找出对应的|X|的值,|X|的集合中的i满足{l_i<=L} \&\& {r_i>=R},就相当于以(L,R)做为原点第二象限的点的个数(包括边界),假设个数为sum,我们要的答案就是max{ sum-\omega (X) }的大小,即sum-(m-R+1+L),就是sum+R-m-L-1,对于这个式子,有三个变量,我们考虑枚举L用线段树动态维护sum+R的最大值,还是很难想的。我看来好久大神的博客才懂了。细节看代码的实现。

代码:

#include<bits/stdc++.h>
#define ls x<<1
#define rs x<<1|1
using namespace std;
const int N=2e5+10;
struct node{
    int l,r,f,mi;
}e[N*4];
void up(int x){
    e[x].mi=max(e[ls].mi,e[rs].mi);
}
void down(int x){
    if(e[x].f==0)return ;
    int f=e[x].f;e[x].f=0;
    e[ls].mi+=f;e[rs].mi+=f;
    e[ls].f+=f;e[rs].f+=f;
}
void built(int x,int l,int r){
    e[x].l=l;e[x].r=r;e[x].f=0;
    if(l==r){
        e[x].mi=l;
        return ;
    }
    int mid=(l+r)/2;
    built(ls,l,mid);built(rs,mid+1,r);
    up(x);
}
void add(int x,int LL,int RR,int v){
    if(e[x].l>=LL&&e[x].r<=RR){
        e[x].f+=v;
        e[x].mi+=v;return ;
    }
    down(x);
    int mid=(e[x].l+e[x].r)/2;
    if(LL<=mid)add(ls,LL,RR,v);
    if(RR>mid)add(rs,LL,RR,v);
    up(x);
}
int query(int x,int LL,int RR){
    int ans=0;
    if(e[x].l>=LL&&e[x].r<=RR){
        return e[x].mi;
    }
    down(x);
    int mid=(e[x].l+e[x].r)/2;
    if(LL<=mid)ans=max(ans,query(ls,LL,RR));
    if(RR>mid)ans=max(ans,query(rs,LL,RR));
    return ans;
}
int n,m;
vector<int>V[N];
int main()
{
    int ans=0;
    scanf("%d%d",&n,&m);
    for(int i=1,a,b;i<=n;i++){
        scanf("%d%d",&a,&b);
        V[a].push_back(b);
    }
    built(1,0,m+1);
    for(int i=0;i<=m+1;i++){
        for(int j=0;j<V[i].size();j++){//枚举l
            add(1,0,V[i][j],1);//记录在y轴上的贡献
        }
        ans=max(ans,query(1,i+1,m+1)-i-m-1);//查询最大值
    }
    cout<<max(ans,n-m)<<endl;
}

arc076E Connected?(思维+栈模拟)

题意:

在一个x*y的网格上,都n个点对,要在每一个点对之间连一条线,使得所有的线在网格之内且不相交。

思路

首先先排除点对不在边界上的情况,因为如果有一个点不在边界,那么一定可以通过找缝隙进行连接。那么我们只要考虑两个点都在边界上的情况。通过画图我们发现,每一个点的出现顺序和是否出现相交有关,所以我们总结一下发现,以顺时针从左上角开始扫每一个点,如果这一个点是一个线段的起点,那么就把这个线段的终点放到栈中,如果是终点就判断,栈顶的元素是不是本身,如果不是那么必然交叉。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
struct node{
    int x,y,x1,y1,id;//id=0表示起点,x1,y1表示其对应的终点
    node(int a=0,int b=0,int e=0,int f=0,int c=0){x=a,y=b,id=c;x1=e;y1=f;}
}e[N];
int n,m,k;
int cl(node p){//在边界
    if(p.x==0)return 0;
    else if(p.y==m)return 1;
    else if(p.x==n)return 2;
    else if(p.y==0)return 3;
    else return -1;
}
bool cmp(node a,node b){//顺时针排序
    int t1=cl(a),t2=cl(b);
    if(t1!=t2){
        return t1<t2;
    }else {
        if(t1==0){
            return a.y<b.y;
        }else if(t1==1){
            return a.x<b.x;
        }else if(t1==2){
            return a.y>b.y;
        }else if(t2==3){
            return a.x>b.x;
        }
    }
}
stack<pair<int,int> >q;
int main()
{
    scanf("%d%d%d",&n,&m,&k);int cnt=0;
    for(int i=1,x1,x2,y1,y2;i<=k;i++){
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        node p1=node(x1,y1,0,0,0);node p2=node(x2,y2,0,0,0);
        if(cl(p1)!=-1&&cl(p2)!=-1){
            ++cnt;
            if(cmp(p1,p2)){
                p1.x1=p2.x,p1.y1=p2.y;
                p1.id=0,p2.id=1;e[cnt]=p1,e[++cnt]=p2;
            }else{
                p2.x1=p1.x,p2.y1=p1.y;
                p1.id=1,p2.id=0;e[cnt]=p1,e[++cnt]=p2;
            }
        }
    }
    sort(e+1,e+cnt+1,cmp);int flag=0;
    for(int i=1;i<=cnt&&!flag;i++){
        if(e[i].id==0){
            q.push(make_pair(e[i].x1,e[i].y1));
        }else{
            if(q.empty())flag=1;
            else{
                pair<int,int>now=q.top();q.pop();
                if(now.first==e[i].x&&now.second==e[i].y){
                    continue;
                }else{
                    flag=1;
                }
            }
        }
    }
    if(flag)puts("NO");
    else puts("YES");
}

abc139F Engines(向量+极角排序)

题意:

给定n的点对(x,y),起点在(0,0),可以选择任意数量的点对使用,每个点对最多只能用一次,设当前坐标为(X,Y),用了

(x,y)就变成了(X+x,Y+y),问最后终点离原点的最大距离。

思路

通过画图可以发现,使用应该点对就是一个向量的加法,那么要使得和向量模最大,就要让夹角尽量小的向量相加,我们考虑将对点进行极角排序。枚举起始点不断更新答案即可,因为使用的向量一定是一个连续的区间。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=200;
struct node{
    double x,y;
}e[N];
bool cmp(const node &a,const node &b){//极角排序
    return atan2(a.y,a.x)<atan2(b.y,b.x);
}
int n;
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)cin>>e[i].x>>e[i].y;
    sort(e,e+n,cmp);
    double ans=0;
    for(int i=0;i<n;i++){
        double x=e[i].x,y=e[i].y;
        ans=max(ans,x*x+y*y);
        for(int j=(i+1)%n;j!=i;j=(j+1)%n){
            x+=e[j].x,y+=e[j].y;
            ans=max(ans,x*x+y*y);
        }
    }
    printf("%.20f\n",sqrt(ans));
}

abc140E Second Sum(set)

题意:

给定一个排列,所有的长度大于等于2的区间,求其区间内第二大值之和。

思路

我们可以对每一个数求出它在答案中得贡献,我们对于数字i在posi位置,我们求出在它右边第一个大于它得数字得位置x1,第二个大于它得数字得位置x2,同理求出左边得y1,y2,那么数字i得贡献就为(x2-x1)*(posi-y1)*i+(y1-y2)*(x1-posi)*i。重点使如何得到x1,x2,y1,y2。我们可以用set,数字从大到小将其位置pi插入set,每次在set中查找比pi第一个比pi大得值,它就是x1,后面一个就是x2,对于y1,y2可以取反进行同样得操作。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10;
set<int>s1,s2;
//set<int>::iterator it;
int a[N],pos[N];
int n;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);pos[a[i]]=i;
	}
	ll ans=0;
	for(int i=n;i>=1;i--){
		int x1=pos[i],y1=pos[i],y2=0,x2=n+1;
		if(s1.size()>=1){
			auto it =s1.upper_bound(pos[i]);
			if(it!=s1.end()){
				x1=*it;
				it++;
				if(it!=s1.end()){
					x2=(*it);
				}
			}
		}
		if(s2.size()>=1){
			auto it=s2.upper_bound(-pos[i]);
			if(it!=s2.end()){
				y1=-(*it);
				it++;
				if(it!=s2.end()){
					y2=-(*it);	
				}
			}
		}
		//cout<<x1<<" "<<x2<<" "<<y1<<" "<<y2<<endl;
		s2.insert(-pos[i]);
		s1.insert(pos[i]);
		if(x1==pos[i]&&y1==pos[i])continue;
		if(x1==pos[i]){
			ans+=1ll*(n-pos[i]+1)*(y1-y2)*i;
		}else if(y1==pos[i]){
			ans+=1ll*(pos[i])*(x2-x1)*i;
		}
		else {
			ans+=1ll*(y1-y2)*(x1-pos[i])*i;
			ans+=1ll*(x2-x1)*(pos[i]-y1)*i;
		}
		
	}
	cout<<ans<<endl;
}

猜你喜欢

转载自blog.csdn.net/qq_40400202/article/details/99649279
今日推荐