感觉思维不行,就打算在atcoder上刷刷题
目录
abc091D Two Sequences(位运算+二分+思维)
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,表示,问如何有用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的异或和。
思路:
首先由于这n有1e5,不能够暴力,那么既然分别考虑答案的每一位,对于第k位,我们只要将a,b中的数字模上(1<<(k+1)),因为只有后面的k位会对第k位有影响,我们将b数组排序,对于每一个ai进行统计,首先,那么如果
就没有贡献,而
也没有贡献,用二分来统计一下即可。通过个数的奇偶更新答案。
总的时间复杂度为
代码:
#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个人只能做的椅子,问要是每个人都能坐下,最多添加多少个椅子。
思路:
首先这是我们只要求出最多能坐X个人,把n-X就是答案了,一个二分图最大匹配的问题,但是按照朴素的做法,必定是TLE。
我们由霍尔定理得到
一个二分图存在完美匹配(每个人都可以坐在椅子上),要满足
,
是左边的任意子集的大小,
是在X的子集中都可以匹配的右边集合的大小。
那么我们可以知道,当max{ }=0的时候存在完美匹配,推广一下,max{
}就是要添加的椅子数,即答案。但是|X|是任意子集,枚举必然超时,我们考虑
的形式发现其必然是
,我们可以枚举(L,R),找出对应的|X|的值,|X|的集合中的i满足
,就相当于以(L,R)做为原点第二象限的点的个数(包括边界),假设个数为sum,我们要的答案就是max{ sum-
}的大小,即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;
}