2023.10.5测试

\[\text{NOIP模拟赛-2023.10.5} \]

T1 魔法少女

定义 \(f(i)\)\(i\) 所有约数的异或和,求 \(f(1)\sim f(n)\) 的异或和

\(1\leq n\leq 10^{14}\)

容易想到枚举约数然后计算出约数出现的次数并对答案做贡献,复杂度 \(\mathcal{O}(n)\)

发现约数 \(x\) 出现的次数即 \(\left\lfloor\dfrac{n}{x}\right\rfloor\),这可以用整除分块求出出现次数相同的一段区间 \([l,r]\),复杂度 \(\mathcal{O}(\sqrt{n})\)

问题是如何快速计算一段区间异或和??

考场思路是,发现两个相邻的偶数和奇数的异或和为 \(1\),所以知道一段区间的头或尾再考虑一下区间长度就可以 \(\mathcal{O}(1)\) 计算出来

当然你打表就可以发现 \(1\sim x\) 的异或和与 \(x\bmod 4\) 的结果有关

code
#include<bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
#define LL long long 
using namespace std;

LL n,ans;

int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	
	scanf("%lld",&n);
	
	for(LL l=1,r; l<=n; l=r+1)
	{
		r=n/(n/l);
		LL tmp=n/l,cnt=r-l+1;
		if(tmp%2==0)
			continue;
		if((l&1) && (r&1))
			ans^=l,cnt--;
		else if((l&1) && (r%2==0))	
			ans^=l^r,cnt-=2;
		else if((l%2==0) && (r%2==0))
			 ans^=r,cnt--;
		ans^=((cnt/2)%2); 
	} 
	
	printf("%lld",ans);

	return 0;
}

T2 亿只只因的回家路

UOJ #747.【UNR #6】面基之路

出题人???

注意到一个关键的性质,题意等价于让所有人同时到同一个点的最短时间,没看出来的话很难做

那这样我们就可以枚举他们的汇合点或者汇合边。在点上汇合比较容易,考虑在边上汇合的情况。只要枚举每个人是从边的左侧还是右侧进入即可,复杂度可以做到 \(\mathcal{O}(km\log n+kn2^k+m2^k)\),可以获得 \(85\)

其实处理点的情况可以做到 \(\mathcal{O}(km\log n+kn)\),但为了处理边的情况多了一个 \(2^k\),所以瓶颈在于处理边的情况

我们一开始是枚举每个人从哪侧进入,但最后只考虑进入边时间最晚的人,所以一定存在一种方案,将人进入左侧的时间从小到大排序后,一个前缀从左侧进入,剩余的后缀从右侧进入,时间复杂度 \(\mathcal{O}(km\log n+kn+mk\log k)\),可以通过

(在 GJOJ 被卡了……)

code
#include<bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
#define LL long long
#define pli pair<LL,int>
using namespace std;

const int N=1e5+10,M=2e5+10,K=20+10;
const LL INF=1e18;

int n,m,k,a[N];
LL d[N],ans1[N][K],ans2[M],ans=INF;
struct node{int y,z;};vector <node> g[N];
struct E{int x,y,z;}e[M];
bool v[N];

void dijkstra(int s)
{
	priority_queue <pli> q;
	memset(v,0,sizeof(v));
	memset(d,0x3f,sizeof(d));
	d[s]=0;  q.push({0,s});
	while(q.size())
	{
		int x=q.top().second;  q.pop();
		if(v[x])
			continue;
		v[x]=1;
		for(auto v:g[x])
		{
			if(d[v.y]>d[x]+(LL)v.z)
			{
				d[v.y]=d[x]+(LL)v.z;
				q.push({-d[v.y],v.y});
			}
		} 
	}
}

LL calc(LL a,LL b,LL z)
{
	if(min(a,b)+z<=max(a,b))
		return max(a,b);
	z-=max(a,b)-min(a,b);
	return max(a,b)+z/2; 
}

int main()
{
    freopen("b.in","r",stdin);
    freopen("b.out","w",stdout);
    
	memset(ans2,0x3f,sizeof(ans2));
	
	scanf("%d%d",&n,&m,&k);
	for(int i=1; i<=m; i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		z*=2;
		e[i]={x,y,z};
		g[x].push_back({y,z});
		g[y].push_back({x,z});
	}
	scanf("%d",&k);
	a[1]=1;  k++;
	for(int i=2; i<=k; i++)
		scanf("%d",&a[i]);
	
	int S=(1<<k)-1;
	for(int i=1; i<=k; i++)
	{
		dijkstra(a[i]);
		
		for(int j=1; j<=n; j++)
			ans1[j][i]=d[j],ans1[j][0]=max(ans1[j][0],d[j]);
	}

	for(int i=1; i<=n; i++)
		ans=min(ans,ans1[i][0]);
	for(int i=1; i<=m; i++)
	{
		priority_queue <pli> q1,q2;
		for(int j=1; j<=k; j++)
			q1.push({ans1[e[i].x][j],j});
		while(q1.size())
		{
			int x=q1.top().second;  q1.pop();
			q2.push({ans1[e[i].y][x],x});
			ans2[i]=min(ans2[i],calc(q1.top().first,q2.top().first,e[i].z));
		}
		ans=min(ans,ans2[i]);
	}
	
	printf("%lld",ans);
	
	return 0;
}

T3 西琳的魔法字符串

CCPC Finals 2021 I. Reverse LIS

一道题炸出很多前置题

这个题有一个弱化版 CF933A A Twisty Movement。但那题用 \(\rm dp\) 可以通过

这里我们转化一下题意,我们枚举 \(0\)\(1\) 的断点 \(i\),那最后的不下降子序列就是 \(i\) 前面的 \(0\) 的个数加上 \(i\) 后面的 \(1\) 的个数。如果我们将 \(0\) 的权值赋成 \(1\)\(1\) 的权值赋成 \(-1\),设权值数组为 \(a\),那么子序列的长度就是 \(\text{count}(S,\,'1')+\sum a[1:i]\)。所以我们就需最大化 \(\sum a[1:i]\)

回到原问题,它支持 \(k\) 次区间翻转的操作,最后最大化一个前缀和。其实可以转化为在原串中选出 \(k+1\) 个不相交的区间,其中一段为前缀,使得子段和最大

这个可以参考 [DS/贪心记录] CF280D k-Maximum Subsequence Sum 的实现

code
#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N=2e5+10;
const LL INF=1e18;

int n,q,a[N];
LL last,ans[N];
queue < pair<int,int> > qq;
struct Ask{int k,id;}Q[N];

struct node{int l,r;LL s;};  node zinf={0,0,INF},finf={0,0,-INF};
node operator + (const node &a,const node &b){return {a.l,b.r,a.s+b.s};}
bool operator < (const node &a,const node &b){return a.s<b.s;}

#define lc(p) p<<1
#define rc(p) p<<1|1
struct Seg
{
	node mx,mn,lmx,rmx,lmn,rmn,sum;
	int rev;  
	#define rev(x) tree[x].rev 
	
	void write(int l,int r,LL s)
	{
		mx=mn=lmx=rmx=lmn=rmn=sum={l,r,s};
		rev=0;
	}
	
	void reverse()
	{
		swap(mx,mn);  swap(lmx,lmn);  swap(rmx,rmn);
		mx.s*=-1;  mn.s*=-1;
		lmx.s*=-1;  rmx.s*=-1;
		lmn.s*=-1;  rmn.s*=-1;
		sum.s*=-1;  rev^=1;
	}
}tree[N<<2];
Seg I={finf,zinf,finf,finf,zinf,zinf,{0,0,0},0};

Seg pushup(Seg p,Seg q)
{
	Seg val=I;
	if(p.mx.s>=1e16)
		return q;
	if(q.mx.s>=1e16)
		return p;
	val.mx=max(max(p.mx,q.mx),p.rmx+q.lmx);
	val.mn=min(min(p.mn,q.mn),p.rmn+q.lmn);
	val.lmx=max(p.lmx,p.sum+q.lmx);
	val.rmx=max(q.rmx,p.rmx+q.sum);
	val.lmn=min(p.lmn,p.sum+q.lmn);
	val.rmn=min(q.rmn,p.rmn+q.sum);
	val.sum=p.sum+q.sum;
	return val;
}

void spread(int p)
{
	if(rev(p))
	{
		tree[lc(p)].reverse();
		tree[rc(p)].reverse();
		rev(p)=0; 
	}
}

void build(int p,int l,int r)
{
	if(l==r)
	{
		tree[p].write(l,r,a[l]);
		return;
	}
	int mid=(l+r)>>1;
	build(lc(p),l,mid);
	build(rc(p),mid+1,r);
	tree[p]=pushup(tree[lc(p)],tree[rc(p)]); 
}

void change(int p,int l,int r,int x,int v)
{
	if(l==r)
	{
		tree[p].write(l,r,v);
		return;
	}
	spread(p);
	int mid=(l+r)>>1;
	if(x<=mid)
		change(lc(p),l,mid,x,v);
	else
		change(rc(p),mid+1,r,x,v);
	tree[p]=pushup(tree[lc(p)],tree[rc(p)]);
}

void reverse(int p,int l,int r,int ql,int qr)
{
	if(ql<=l && qr>=r)
		return tree[p].reverse(),void();
	spread(p);
	int mid=(l+r)>>1;
	if(ql<=mid)
		reverse(lc(p),l,mid,ql,qr);
	if(qr>mid) 
		reverse(rc(p),mid+1,r,ql,qr);
	tree[p]=pushup(tree[lc(p)],tree[rc(p)]); 
}

int main()
{
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);
    
	scanf("%d",&n);
	for(int i=1; i<=n; i++)
	{
		int c,p;
		scanf("%d%d",&c,&p);
		a[i]=(c==0? 1:-1)*p;
		if(c==1)
			last+=(LL)p;
	}
	
	build(1,1,n);
	
	Seg tur=tree[1];
	if(tur.lmx.s>0)
	{
		last+=tur.lmx.s;
		reverse(1,1,n,tur.lmx.l,tur.lmx.r);
	}
	
	scanf("%d",&q);
	for(int i=1; i<=q; i++)
		scanf("%d",&Q[i].k),Q[i].id=i;
	
	sort(Q+1,Q+1+q,[](Ask a,Ask b){return a.k<b.k;});
	
	int cnt=0;
	for(int i=1; i<=q; i++)
	{
		while(cnt<Q[i].k)
		{
			Seg cur=tree[1];
			if(cur.mx.s<=0)
				break;
			last+=cur.mx.s; 
			reverse(1,1,n,cur.mx.l,cur.mx.r);
			qq.push({cur.mx.l,cur.mx.r});
			cnt++;
		}
		ans[Q[i].id]=last;
	}
	
	for(int i=1; i<=q; i++)
		printf("%lld\n",ans[i]);
	
	return 0;
}

/*
1
1 1
1
0
*/ 

\(100+80+0+0=180\)\(\rm rk16\)

posted @ 2023-10-06 16:10  xishanmeigao  阅读(49)  评论(0)    收藏  举报