2019CCPC网络赛部分题解

这场锅最大的应该是我吧。。。一个半小时的时候我已经写完了08的正解,然而突发奇想也没造数据就把自己叉了,然后带着学弟想了俩小时这道傻逼题。。。

三道签到题我都没看题,学弟一个人写的;wtw不在学校,抽空回酒店写了03,原话“这道题我看了30s就会了”;我的本场唯一贡献08,加上一大段时间的负输出。

最后五题滚粗有点难受,有些题压根没时间搞。。。

1002 array

这道题似乎做法很多,我最初以为必须要主席树,就放弃思考了。。。

说下set+线段树的做法吧。

首先因为k<=n,可知答案必定在[k,n+1]中。

对于操作1,将某个位置的值加上1e7其实和删去原来的值没有差别,因为n<=1e5,加上1e7后就不可能成为答案。

对于操作2,查询未在序列[1,r]位置出现过的,大于等于k的最小值。

因为给的初始序列是一个排列,令n+1位置处的值为n+1,那么可能的答案其实就是:

1.  [1,r]中删去的(如果有的话)恰好大于等于k的值;

2.  [r+1,n+1]所有值中(不论是否删去)恰好大于等于k的值。

那么其实就等价于

1.  [1,n+1]中删去的恰好大于等于k的值;

2.  [r+1,n+1]所有值中恰好大于等于k的值。

这样就很容易维护了。

对于1,将所有删去的位置的数都放进set中,每次通过二分查找到一个恰好大于等于k的值。

对于2,建一棵权值线段树,存每个值所在的下标,维护区间最大值。可知需要在[k,n+1]区间找到一个下标大于r的最左端的位置,那么查询的时候就可以在线段树上进行二分。

将得到的两个值取较小值即可,时间复杂度O(nlogn)。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ls (rt<<1)
#define rs (rt<<1|1)
const int maxn=1e5+5;
struct node{
	int l,r,maxx;
}Q[maxn<<2];
set<int> st;
set<int>::iterator it;
int T,n,m;
int op,p,r,k,ans,a[maxn],b[maxn];
void build(int rt,int l,int r){
	Q[rt].l=l,Q[rt].r=r;
	if(Q[rt].l==Q[rt].r){
		Q[rt].maxx=b[l];return;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	Q[rt].maxx=max(Q[ls].maxx,Q[rs].maxx);
}
int query(int rt,int l,int p){//[l,n+1]中最小且位置大于等于p的 
	if(Q[rt].l==Q[rt].r){
		if(Q[rt].maxx>=p) return Q[rt].l;
		else return 0;
	}
	int mid=(Q[rt].l+Q[rt].r)>>1;
	if(mid<l) return query(rs,l,p);
	else{
		int res=0;
		if(Q[ls].maxx>=p) res=query(ls,l,p);
		if(res==0&&Q[rs].maxx>=p) res=query(rs,l,p);
		return res;
	}
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d %d",&n,&m);
		ans=0;
		st.clear();
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[a[i]]=i;
		b[n+1]=n+1;
		build(1,1,n+1);
		for(int i=1;i<=m;i++){
			scanf("%d",&op);
			if(op==1){
				scanf("%d",&p);
				p^=ans;
				st.insert(a[p]);
			}
			else{
				scanf("%d %d",&r,&k);
				r^=ans,k^=ans;
				it=st.lower_bound(k);
				if(it!=st.end()) ans=*it;
				else ans=n+1;
				ans=min(ans,query(1,k,r+1));
				cout<<ans<<"\n";
			}
		}
	}
	return 0;
}

1004 Path

不会算这个复杂度,我觉得自己的做法是个暴力。。。

结束前二十多分钟想了下这道题,只能想到用multiset维护maxk条路径长度暴力。想莽一发但是没写完。

后来写完就T了,因为maxk(k的最大值)直接设的5e4,改成每组真正的maxk之后就过了。

先将每个点的邻接边按边长排序,可能算是一个小优化。

将路径按长度排序,先把所有单独的路径放入multiset,然后取当前的最短的路径进行更新,若路径总数小于maxk,无脑放入当前的multiset;若路径总数已经达到maxk且更新后的路径已经比最长的路径还长,则不用继续更新,将当前路径放入另一个multiset中;若路径总数已经达到maxk且更新后的路径比最长的路径短,将最长的路径踢出去,把更新的路径放入当前multiset;若当前路径的所有后继已更新完,也放入另一个multiset中。

最后需要的maxk条路径都会在另一个multiset中。时间复杂度未知。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=5e4+5;
struct node{
	int u;
	ll w;
	node(int _u=0,ll _w=0):u(_u),w(_w){} 
	bool operator <(const node &p)const{
		return w<p.w;
	}
}tmp;
multiset<node> mst,mst1;
multiset<node>::iterator it,it1;
int n,m,q,maxk,k[maxn],u,v,w,T;
vector<node> G[maxn];
ll ans[maxn];
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d %d %d",&n,&m,&q);
		for(int i=1;i<=n;i++) G[i].clear();
		maxk=0;
		mst.clear();
		mst1.clear();
		for(int i=1;i<=m;i++){
			scanf("%d %d %d",&u,&v,&w);
			G[u].push_back(node(v,w));
			mst.insert(node(v,w));
		}
		for(int i=1;i<=n;i++) sort(G[i].begin(),G[i].end());
		for(int i=1;i<=q;i++){
			scanf("%d",&k[i]);
			maxk=max(maxk,k[i]);
		}
		while(mst.size()){
			it=mst.begin();
			for(node tt:G[(*it).u]){
				tmp.u=tt.u;
				tmp.w=tt.w+(*it).w;
				if(mst.size()+mst1.size()<maxk) mst.insert(tmp);
				else{
					it1=mst.end();
					it1--;
					if(tmp.w>=(*it1).w) break;
					else{
						mst.erase(it1);
						mst.insert(tmp);
					}
				}
			}
			mst1.insert(*it);
			mst.erase(it);
		}
		u=0;
		for(it=mst1.begin();it!=mst1.end();it++) ans[++u]=(*it).w;
		for(int i=1;i<=q;i++) cout<<ans[k[i]]<<"\n";
	}
    return 0;
}

1005 huntian oy

要解决本题首先要知道

似乎是去年多校的时候见到了这个式子,就记在了模板里,晚上补题一看到题目就翻到了。

因为a、b互质,所以所求为:

\sum_{i=1}^{n}\sum_{j=1}^{i}(i^{gcd(a,b)}-j^{gcd(a,b)})[gcd(i,j)=1]=\sum_{i=1}^{n}\sum_{j=1}^{i}(i-j)[gcd(i,j)=1]

接下来进行莫比乌斯反演,易知所求为f(1)

f(d)=\sum_{i=1}^{n}\sum_{j=1}^{i}(i-j)[gcd(i,j)=d]=d\sum_{i=1}^{\left \lfloor \frac{n}{d}\right \rfloor}\sum_{j=1}^{i}(i-j)[gcd(i,j)=1]

F(d)=\sum_{d|k,k \leq n}f(k)=d\sum_{i=1}^{\left \lfloor \frac{n}{d} \right \rfloor}\sum_{j=1}^{i}(i-j)\\=d\sum_{i=1}^{\left \lfloor \frac{n}{d} \right \rfloor}(i\times i-\frac{i\times (i+1)}{2}) \\=d\sum_{i=1}^{\left \lfloor \frac{n}{d} \right \rfloor}(\frac{i\times (i-1)}{2})\\=d\times \frac{(\left \lfloor \frac{n}{d} \right \rfloor -1)\times (\left \lfloor \frac{n}{d} \right \rfloor)\times (\left \lfloor \frac{n}{d} \right \rfloor +1)}{6}

所以有

f(d)=\sum_{d|k,k \leq n}\mu(\frac{k}{d})\times F(k)

f(1)=\sum_{i=1}^{n}\mu(i)\times F(i)=\sum_{i=1}^{n}\mu(i)\times i\times \frac{(\left \lfloor \frac{n}{i} \right \rfloor -1)\times (\left \lfloor \frac{n}{i} \right \rfloor)\times (\left \lfloor \frac{n}{i} \right \rfloor +1)}{6}

后面那部分显然可以数论分块,因此需要想办法求

S(n)=\sum_{i=1}^{n}\mu(i)\times i

开头我把这玩意当成\mu(i)*id(i)(二者的狄雷克利卷积)了,显然不是。。。前缀和不好求,那么考虑对\mu(i)\times i进行狄雷克利卷积,凑出前缀和好求的积性函数。

这里可以卷个id(i),则有

(\mu(i)\times i) *id(i)=\sum_{d|i}\mu(d)\times d\times \frac{i}{d}=i\sum_{d|i}\mu(i)=i[i=1]

因此有

\large 1=\sum_{i=1}^{n}i[i=1]=\sum_{i=1}^{n}\sum_{d|i}\mu(d)\times d\times \frac{i}{d}=\sum_{\frac{i}{d}=1}^{n}\frac{i}{d}\sum_{d=1}^{\left \lfloor \frac{n}{\frac{i}{d}}\right \rfloor} \mu(d)\times d\\=\sum_{i=1}^{n}i\times S(\left \lfloor \frac{n}{i}\right \rfloor)

\large S(n)=1-\sum_{i=2}^{n}i\times S(\left \lfloor \frac{n}{i}\right \rfloor)

后面那部分可以数论分块,所以可以用杜教筛来求S(n)。

至此,本题就解决了。线性筛预处理前\large n^{\frac{2}{3}}的S(n),则杜教筛时间复杂度为\large O(n^{\frac{2}{3}})

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod=1e9+7;
const int maxn=1e6+5;
int T,n,a,b;
int prime[maxn],cnt,mu[maxn];
bool notprime[maxn];
inline void init(){
	notprime[1]=1;
	mu[1]=1;
	for(int i=2;i<maxn;i++){
		if(!notprime[i]) prime[cnt++]=i,mu[i]=-1;
		for(int j=0;j<cnt&&prime[j]*i<maxn;j++){
			notprime[prime[j]*i]=1;
			if(i%prime[j]==0){
				break;
			}
			mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=1;i<maxn;i++){
		mu[i]=(mu[i]*1ll*i+mu[i-1])%mod;
		if(mu[i]<mod) mu[i]+=mod;
	}
}
unordered_map<int,int> ump;
int S(int x){
	if(x<maxn) return mu[x];
	if(ump.find(x)!=ump.end()) return ump[x];
	int res=1;
	for(int l=2,r,tt;l<=x;l=r+1){
		tt=x/l;
		r=x/tt; 
		res=(res-(r-l+1)*1ll*(r+l)/2%mod*S(tt))%mod;
	}
	if(res<0) res+=mod;
	return ump[x]=res;
}
int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=res*1ll*a%mod;
		b>>=1;
		a=a*1ll*a%mod;
	}
	return res;
}
int inv=qpow(6,mod-2);
int main(){
	init();
	scanf("%d",&T);
	while(T--){
		scanf("%d %d %d",&n,&a,&b);
		ll ans=0;
		for(int l=1,r,tt;l<=n;l=r+1){
			tt=n/l;
			r=n/tt;
			ans=(ans+(S(r)-S(l-1))*((tt-1)*1ll*tt%mod*(tt+1)%mod*inv%mod))%mod;
		}
		if(ans<0) ans+=mod;
		cout<<ans<<"\n";
	}
	return 0;
}

1008 Fishing Master

令sumk=n*k,sumt=sum(ti)+k,分别为初始的钓鱼的结束时间和炖鱼的结束时间。

考虑到炖一条鱼的时候渔夫有两种选择,一种是多等待ti%k,使得钓鱼的最后结束时间推后;另一种是浪费k-ti%k,使炖鱼的最后结束时间推后。我们需要的是使得两者的最大值最小。

因为ti%k从小往大的时候k-ti%k必然从大往小,如果设定一个最终结束时间的话,就会存在贪心策略,多等待的时间一定是按ti%k从小往大选,浪费的时间也一定是按k-ti%k从小往大选。

对每个ti处理出这两个数,然后按其中一个排序。比如按ti%k从小到大排序。

二分最终结束时间,贪心的从小到大选择ti%k,直到sumk再增加就会超过最终结束时间,剩下的就都认为是将k-ti%k加给sumt。若二者都满足小于等于最终结束时间,则缩小二分上界;否则增大下界。

初始的二分下界可以为max(sumk,sumt),上界可以为sumk+sumt-k。

时间复杂度O(nlogn)。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+5;
int T,t[maxn],k,n,cnt;
struct node{
    int a,b;
    bool operator<(const node &p)const{return a<p.a;}
}c[maxn];
ll sumk,sumt;
inline bool check(ll mid){
    ll rk=sumk,rt=sumt;
    for(int i=1;i<=cnt;i++){
        if(rk+c[i].a<=mid) rk+=c[i].a;
        else rt+=c[i].b;
    }
    return rt<=mid;
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d %d",&n,&k);
        cnt=0,sumt=k,sumk=n*1ll*k;
        for(int i=1;i<=n;i++){
            scanf("%d",&t[i]);
            sumt+=t[i];
            if(t[i]%k==0) continue;
            cnt++;
            c[cnt].a=t[i]%k,c[cnt].b=k-c[cnt].a;
        }
        sort(c+1,c+1+cnt);
        ll l=max(sumk,sumt),r=sumk+sumt-k,mid,now;
        while(l<=r){
            mid=(l+r)/2;
            if(check(mid)) r=mid-1,now=mid;
            else l=mid+1;
        }
        cout<<now<<"\n";
    }
    return 0;
}

写题解的时候常常可以产生新的想法。。。对于本题,既然贪心策略存在,那么其实可以不用二分。

最后二分部分改为:正着对ti%k做一遍前缀和,再反着将k-ti%k相加,考虑以当前位置为分界时的最终结束时间,维护最小值即可。

当然,因为要排序,时间复杂度其实不变,还是O(nlogn),只是常数小。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+5;
int T,t[maxn],k,n,cnt;
struct node{
    int a,b;
    bool operator<(const node &p)const{return a<p.a;}
}c[maxn];
ll sumk,sumt,sum[maxn],s,ans;
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d %d",&n,&k);
        cnt=0,s=0,sumt=k,sumk=n*1ll*k;
        for(int i=1;i<=n;i++){
            scanf("%d",&t[i]);
            sumt+=t[i];
            if(t[i]%k==0) continue;
            cnt++;
            c[cnt].a=t[i]%k,c[cnt].b=k-c[cnt].a;
        }
        sort(c+1,c+1+cnt);
        for(int i=1;i<=cnt;i++) sum[i]=sum[i-1]+c[i].a;
        ans=max(sumk+sum[cnt],sumt);
        for(int i=cnt;i>=1;i--){
            s+=c[i].b;
            ans=min(ans,max(sumk+sum[i-1],s+sumt));
        }
        cout<<ans<<"\n";
    }
    return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值