20190807杭电多校第六场

本文深入探讨了算法竞赛中的高效解题策略,包括使用树状数组优化最长递增子序列问题,利用线段树求解最大子段和,以及通过预处理和枚举技巧解决复杂的数学问题。文章提供了详细的代码实现和时间复杂度分析。

1002 Nonsense Time

赛中和队友一直在搞一个胡搞做法,艰难优化到本地跑10s,可惜始终过不了。

最后只好补了题解做法。。。

倒着删除,用树状数组求LIS,维护一条当前的LIS的路径。若被删除的数不在当前的LIS中,则答案不变;否则,重新做一遍LIS。

据说因为LIS的期望长度为sqrt(n),所以被选中的概率是\frac{\sqrt n}{n},所以总时间复杂度为O(\sqrt n*nlogn)

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int maxn=5e4+100;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int seed=131;
int t,n,ans,c[maxn],pos[maxn],pre[maxn],maxp;
int p[maxn],k[maxn],num,pp;
bool vis[maxn];
inline int lowbit(int x){
	return x&(-x);
}
inline void modify(int x,int z){
	int tmp=x;
	while(tmp<=n){
		if(z>c[tmp]) c[tmp]=z,pos[tmp]=x;
		tmp+=lowbit(tmp);
	}
}
inline void query(int x){
	while(x){
		if(c[x]>num) num=c[x],pp=pos[x];
		x-=lowbit(x);
	}
}
inline void solve(){
	ans=0,maxp=0;
	memset(c,0,sizeof(c));
	memset(vis,0,sizeof(vis));
	memset(pos,0,sizeof(pos));
	for(int i=1;i<=n;i++){
		if(p[i]){
			num=0,pp=0;
			query(p[i]-1);
			pre[p[i]]=pp;
			modify(p[i],num+1);
			if(num+1>ans){
				ans=num+1,maxp=p[i];
			}
		}
	}
	while(maxp){
		vis[maxp]=1;
		maxp=pre[maxp];
	}
}
int out[maxn];
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		for(int i=1;i<=n;i++) scanf("%d",&p[i]);
		for(int i=1;i<=n;i++) scanf("%d",&k[i]);
		solve();
		for(int i=n;i>=1;i--){
			out[i]=ans;
			if(vis[p[k[i]]]){
				p[k[i]]=0;
				solve();
			}
			else p[k[i]]=0;
		}
		for(int i=1;i<=n;i++){
			cout<<out[i];
			if(i!=n) cout<<" ";
			else cout<<"\n";
		}
	}
    return 0;
}

1005 Snowy Smile

首先要知道线段树是怎么求最大子段和的:维护当前区间的最大子段和,当前区间从左往右的最大子段和,当前区间从右往左的最大子段和,然后就可以合并了。

对于本题,只有2000个点,时间复杂度O(n^{2}logn)显然是可以的。

想到先对所有点按x排序,将y离散化,并用离散化后在数组中的下标代替原来的值。

按x枚举,先枚举左边的边(即较小的x),当左边的边确定时,右边的边从左往右一列列的扫,同时将w加在对应的y上,用线段树来维护,每次修改都是logn的,每枚举完一列查询一次最大子段和的值,用一个变量维护最大值即可。

枚举左边的边是O(n)的,枚举右边的边、修改线段树中的值和查询的总过程可认为是O(nlogn)的,因此总的时间复杂度为O(n^{2}logn)

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int maxn=2e3+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int seed=131;
int t,m,cnt;
ll ans;
struct node{
    int l,r;
    ll sum,maxx,lmax,rmax;
}Q[maxn<<2];
struct Point{
    int x,y,w;
    bool operator<(const Point &p)const{
        return x<p.x||x==p.x&&y<p.y;
    }
}a[maxn];
int yy[maxn];
void build(int rt,int l,int r){
    Q[rt].l=l,Q[rt].r=r,Q[rt].sum=Q[rt].lmax=Q[rt].maxx=Q[rt].rmax=0;
    if(l==r) return ;
    int mid=(Q[rt].l+Q[rt].r)/2;
    build(ls,l,mid);
    build(rs,mid+1,r);
}
inline void pushup(int rt){
    Q[rt].sum=Q[ls].sum+Q[rs].sum;
    Q[rt].maxx=max(max(Q[ls].maxx,Q[rs].maxx),Q[ls].rmax+Q[rs].lmax);
    Q[rt].lmax=max(Q[ls].lmax,Q[ls].sum+Q[rs].lmax);
    Q[rt].rmax=max(Q[rs].rmax,Q[rs].sum+Q[ls].rmax);
}
void show(int rt){
    cout<<Q[rt].l<<" "<<Q[rt].r<<" "<<Q[rt].sum<<" "<<Q[rt].maxx<<"\n";
}
void update(int rt,int p,int x){
//    show(rt);
    if(Q[rt].l==Q[rt].r){
        Q[rt].sum+=x;
        Q[rt].lmax=Q[rt].maxx=Q[rt].rmax=Q[rt].sum;
        return;
    }
    int mid=(Q[rt].l+Q[rt].r)/2;
    if(p<=mid) update(ls,p,x);
    else update(rs,p,x);
    pushup(rt);
}
int rt=1,l=1,r,n;
int main(){
//    freopen("in.txt","r",stdin);
    a[0].x=-1e9-1;
    scanf("%d",&t);
    while(t--){
        ans=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d %d %d",&a[i].x,&a[i].y,&a[i].w),yy[i]=a[i].y;
        sort(yy+1,yy+n+1);
        cnt=unique(yy+1,yy+n+1)-yy;
        sort(a+1,a+1+n);
        for(int i=1;i<=n;i++){
            a[i].y=lower_bound(yy+1,yy+cnt,a[i].y)-yy;
        }
        a[n+1].x=1e9+1;
        for(int i=1;i<=n;i++){
            if(a[i].x==a[i-1].x) continue;    
            rt=1,l=1,r=cnt-1;
            build(rt,l,r);
            for(int j=i;j<=n;j++){
                while(a[j+1].x==a[j].x){
            //        cout<<i<<" "<<j<<" "<<"\n";
                    update(rt,a[j].y,a[j].w);
                    j++;
                }
                update(rt,a[j].y,a[j].w);
                ans=max(ans,Q[1].maxx);
            }
        }
        cout<<ans<<"\n";
    }
    return 0;
}

1006 Faraway

赛中枚举区间部分没有想的很清楚,之所以没有仔细想下去是因为不会做枚举完区间后CRT。。。

不过题解做法不用CRT,就按题解的思路补了这题。

首先考虑如何枚举区间。可以把每个方程的x和y的值看作坐标系上两条分别平行y轴和x轴的线段,另一维的取值为[0,m]。

这样就有至多n条平行于x轴的线段和n条平行于y轴的线段,形成O(n^2)个矩形区域。

区域确定后,所有方程都可以去绝对值。

考虑到lcm(2,3,4,5)=60,即对60取模后相同的值,对2、3、4、5分别取模后值也相同。

因此可以分别枚举x_{e}y_{e}的模数,通过模数对所有方程进行check,如果全部符合要求,则求出该区域中x_{e}对应模数个数和y_{e}对应模数个数的乘积,并加入答案。因为x_{e}y_{e}每种模数的个数是无关的,可以预处理求出对应方向每个区域每种模数的个数。

预处理O(n*60)+区域个数O(n^2)*枚举两个模数O(60^2)*checkO(n)

所以总的时间复杂度O(60^2*n^3)

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int maxn=10+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=60;
const int seed=131;
int t,n,m;
struct node{
	int x,y,k,t;
}a[maxn];
int xx[maxn],yy[maxn],cntx,cnty;
int numx[maxn][65],numy[maxn][65];
inline void init(){//计算区间模数的个数 
	memset(numx,0,sizeof(numx));
	memset(numy,0,sizeof(numy));
	for(int i=0;i<cntx;i++){
		if(xx[i]>xx[i+1]-1) continue;
		for(int j=0;j<mod;j++) numx[i][j]=(xx[i+1]-1)/mod+(j<=(xx[i+1]-1)%mod)-(xx[i]/mod+(j<(xx[i]%mod)));
	}//(xx[i+1]-1)/60
	for(int i=0;i<cnty;i++){
		if(yy[i]>yy[i+1]-1) continue;
		for(int j=0;j<mod;j++) numy[i][j]=(yy[i+1]-1)/mod+(j<=(yy[i+1]-1)%mod)-(yy[i]/mod+(j<(yy[i]%mod)));
	}
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d",&t);
	while(t--){
		scanf("%d %d",&n,&m);
		for(int i=1;i<=n;i++) scanf("%d %d %d %d",&a[i].x,&a[i].y,&a[i].k,&a[i].t),xx[i]=a[i].x,yy[i]=a[i].y;
		sort(xx+1,xx+1+n);
		sort(yy+1,yy+1+n);
		cntx=unique(xx+1,xx+1+n)-xx;
		cnty=unique(yy+1,yy+1+n)-yy;
		xx[cntx]=m+1;
		yy[cnty]=m+1;
		xx[0]=0;
		yy[0]=0;
		init();
		ll ans=0;
		for(int i=0;i<cntx;i++){//[xx[i],xx[i+1]-1]
			if(xx[i]>xx[i+1]-1) continue;
			for(int j=0;j<cnty;j++){//[yy[j],yy[j+1]-1]
				if(yy[j]>yy[j+1]-1) continue;
				for(int m1=0;m1<mod;m1++){
					if(numx[i][m1]==0) continue;
					for(int m2=0;m2<mod;m2++){
						if(numy[j][m2]==0) continue;
						bool f=0;
						for(int z=1;z<=n&&!f;z++){
							int tmp1=m1-a[z].x,tmp2=m2-a[z].y;
							if(xx[i]<a[z].x) tmp1=-tmp1;
							if(yy[j]<a[z].y) tmp2=-tmp2;
							tmp1=((tmp1+tmp2)%a[z].k+a[z].k)%a[z].k;
							//虽然值大于x或y,但是模数可能小于x或y,因此可能出现负值 
							if(tmp1!=a[z].t) f=1;
						}
						if(!f) ans+=numx[i][m1]*1ll*numy[j][m2];
					}
				}
			}
		}
		cout<<ans<<"\n";
	}
    return 0;
}

1008 TDL

显然与n互质的第m个数和n的差值不会太大,随便估计一下2000肯定够用了,那么差值与n的异或相较n的差距也不会超过2000。

所以枚举k\pm 2000,作为n,这样可以得到当前的f(n,m),再从n+1开始枚举到f(n,m)或第m个与n互质的数。当此时的f(n,m)就是第m个和n互质的数即为答案。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int maxn=1e5+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int seed=131;
int t,m,cnt;
ll n,k,ff,tt;
bool f=0;
int main(){
//    freopen("in.txt","r",stdin);
    scanf("%d",&t);
    while(t--){
        f=0;
        scanf("%lld %d",&k,&m);
        for(n=max(1ll,k-2000);n<=k+2000&&!f;n++){
            ff=(k^n)+n;
        //    cout<<n<<" "<<ff<<"\n";
            if(__gcd(ff,n)!=1||ff<m+n) continue;
            cnt=0;
            for(tt=n+1;tt<=ff;tt++){
                if(__gcd(tt,n)==1){
                    cnt++;
                    if(cnt==m) break;
                }
            }
            if(cnt<m) continue;
            else if(tt==ff){
                f=1;break;
            }
        }
        if(!f) n=-1;
        cout<<n<<"\n";
    }
    return 0;
}

1012 Stay Real

签到题。

因为小根堆中最大的值一定是叶子,所以两个人轮流取当前最大的值即可,大根堆维护方便好写。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define m_p make_pair
#define p_b push_back
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ls (rt<<1)
#define rs ((rt<<1)|1)
#define mst(a,b) memset(a,b,sizeof(a))
const int maxn=1e5+5;
const db eps=1e-8;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int seed=131;
int t,n,x,tt;
ll ans[2];
priority_queue<int> pq;
int main(){
//    freopen("in.txt","r",stdin);
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d",&x);
            pq.push(x);
        }
        tt=ans[0]=ans[1]=0;
        while(!pq.empty()){
            ans[tt]+=pq.top();
            pq.pop();
            tt^=1;
        }
        cout<<ans[0]<<" "<<ans[1]<<"\n";
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值