10.7 杂题补做

难度超标 属实逆天
考完全员爆蛋(((

T1

简要题意 定义 \(f_i\) 表示 \(\in x|i\) 的异或和
给定 \(n\)\(1\to n\) 所有 \(f_i\) 的异或和

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

很容易想到枚举每个约数 然后算出现次数异或
时间复杂度 \(O(n)\)
过不去 发现可以用整除分块优化 时间复杂度降成 \(O(\sqrt n)\)

现在的问题转化为怎么快速求出 \(1\to x\) 的异或和
发现有规律 找规律 \(O(1)\) 即可求得

#include<bits/stdc++.h>
#define ll long long
#define reg register 
using namespace std;
ll n,ans;
inline ll get(ll x)
{
	if(x%4==0) return x;
	if(x%4==1) return 1;
	if(x%4==2) return x+1;
	return 0;
}
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);
		if((n/l)&1) ans^=get(r)^get(l-1);
	}
	printf("%lld",ans);
	return 0;
}

T2 亿只只因的回家路

简要题意:一个无向图有 \(n\)\(m\) 边 其中有 \(k\) 个特殊点有只因 每条边的距离是 \(w(w \leq 10^8)\)
\(1\) 点开始按顺序移动接到所有只因 只因也会同时移动

其中 \(n\leq 10^5,m\leq 2\times 10^5,k\leq 20\)

因为所有点都是同时移动的 考虑最后到达的一个相遇位置
可以是点上,也可以是边上
所以 只需要考虑一个相聚点 比较每个只因到这个点的时间的最大值即可
然后是边 边有左右进入两种情况 如果直接 \(2^k\) 枚举时间复杂度炸裂 但是能拿80
不妨贪心 将每个点到这个边左端点距离排序
然后枚举一个切割点 这个切割点左边全部点都走左端点 右边都走右端点
可以证明是最优的

用 dij 预处理好最短路
总时间复杂度 \(O(kn\log n+km\log k)\)
需要卡常

#include<bits/stdc++.h>
#pragma GCC optimize (1)
#pragma GCC optimize (2)
#pragma GCC optimize (3,"Ofast","inline")
#define ll int
#define reg register 
#define N 100005
#define inf 1e9
using namespace std;
inline int max(int a,int b)
{
	return a>b?a:b;
}
inline int min(int a,int b)
{
	return a<b?a:b;
}
int n,m,k;
ll maxx[25],ans=inf;
int d[25][N];
int p[25];
int head[N],tot=1;
struct edge{
	int to,next,w,fr;
}e[N*4];
void add(int u,int v,int w)
{
	e[tot]=(edge){v,head[u],w,u};
	head[u]=tot++;
}
int vis[N];
ll dis[N];
struct point{
	int x;
	ll v;
};
bool operator < (point a,point b)
{
	return a.v>b.v;
}
priority_queue<point> q;
ll dij(int u,int v)//u->v 最短路 
{
	for(reg int i=1;i<=n;i++)
		dis[i]=inf,vis[i]=0;
	dis[u]=0;
	q.push((point){u,0});
	while(!q.empty())
	{
		point x=q.top();
		q.pop();
		if(vis[x.x]) continue;
		vis[x.x]=1;
		for(reg int i=head[x.x];i;i=e[i].next)
		{
			int to=e[i].to;
			if(dis[to]>x.v+e[i].w)
			{
				dis[to]=x.v+e[i].w;
				if(!vis[to]) 
				q.push((point){to,dis[to]});
			}			
		}
	}
	return dis[v];
}
struct node{
	ll l,r;
}b[25];
bool cmp(node a,node b)
{
	return a.l<b.l;
}
int main()
{
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(reg int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	}
	scanf("%d",&k);
	p[++k]=1;
	for(reg int i=1;i<=k;i++)
	{
		scanf("%d",&p[i]);
		dij(p[i],1);
		for(int j=1;j<=n;j++)	
			d[i][j]=dis[j];
	}
	for(reg int i=1;i<=n;i++)
	{
		ll maxx=0;
		for(reg int j=1;j<=k;j++)
			maxx=max(maxx,d[j][i]);
		ans=min(ans,maxx*2);
	}
	for(reg int i=1;i<tot;i+=2)
	{
		int u=e[i].fr,v=e[i].to;
		for(reg int j=1;j<=k;j++)
			b[j].l=d[j][u],b[j].r=d[j][v];
		sort(b+1,b+1+k,cmp);
		for(reg int j=k;j>=1;j--)
			maxx[j]=max(maxx[j+1],b[j].r);
		ll g=inf;
		for(reg int j=1;j<=k;j++)
			g=min(g,max(b[j].l,maxx[j+1])*2+max(e[i].w-abs(b[j].l-maxx[j+1]),0));
		ans=min(ans,g);
	}
	printf("%d",ans);
	return 0;
}

T3 西琳的魔法字符串

简要题意 给出一个 \(01\) 串 有 \(n\)\(01\) 段 有 \(q\) 个询问
若最多翻转 \(k\) 次区间 这个 \(01\) 串的最长不降子序列最长是多少
\(n\leq 10^5,m\leq 10^5,k\leq n\)

这道题思路和码量都不小

1.思路

\(01\)串的最长不降子序列?
等价于枚举一个位置\(pos\) 算出 \(pos\) 前面的 \(0\) 的数量加上后面 \(1\) 的数量
考虑前后很麻烦 不妨改为 \(0\) 的数量加上总体 \(1\) 的数量减去这个位置前面 \(1\) 的数量
柿子就是 : \(val(x)=count(n,1)+count(x,0)-count(x,1)\)

思考到这里 不妨将 \(0\) 的贡献定义为 \(1\),\(1\)的贡献定义为 \(-1\)
将这个数组记为 \(w\)
若不做任何操作 那么最长不降子序列的长度就等价于求 \(w\) 的前缀和最大值

最难的点就是翻转了
思考一下 翻转对答案有什么贡献
我们不妨假定最终选点是 \(x\)

那么 对答案有贡献就是:

  • 1 将 \(x\) 右边的数翻转 这样可以增加其中正数(\(0\to 1\))的贡献 还有加上负数的贡献
  • 2 将 \(x\) 左边的数翻转 这样可以增加其中负数的贡献 \((1\to 0)\) 减去正数贡献

不妨将 \(1\to x\) 取反 每次贪心操作取最大子段和即可
可以发现一开始选区间也是相同操作 所以可以贪心地把最优的区间先取好

2.代码

预处理 \(n\) 次取操作 用线段树维护

  • \(1\).区间最大子段和
  • \(2\).区间取反

因为会取反 因此还要维护区间最小子段和
难点在于如何查找最大子段和的区间是哪一段
可以直接再用 \(16\) 个变量去维护 但是多方面炸裂 打不出

不如直接在线段树上二分 往左右跳找区间
时间复杂度 \(O(n\log n)\)

再也不想写线段树了

#include<bits/stdc++.h>
#define N 200005
#define ll long long
using namespace std;
int n,m;
ll ans[N],a[N],sum[N],s,pos=1;
struct tree{
	ll lmax,rmax,lmin,rmin,sum,max,min;
	int cov;
}tr[N*4];
void Pushup(int x)
{
	tr[x].lmax=max(tr[x*2].lmax,tr[x*2].sum+tr[x*2+1].lmax);
	tr[x].rmax=max(tr[x*2+1].rmax,tr[x*2+1].sum+tr[x*2].rmax);
	tr[x].lmin=min(tr[x*2].lmin,tr[x*2].sum+tr[x*2+1].lmin);
	tr[x].rmin=min(tr[x*2+1].rmin,tr[x*2+1].sum+tr[x*2].rmin);
	tr[x].sum=tr[x*2].sum+tr[x*2+1].sum;
	tr[x].max=max(tr[x*2].max,max(tr[x*2+1].max,tr[x*2].rmax+tr[x*2+1].lmax));
	tr[x].min=min(tr[x*2].min,min(tr[x*2+1].min,tr[x*2].rmin+tr[x*2+1].lmin));
}
void work(tree &x)
{
	x.lmax*=-1;
	x.rmax*=-1;
	x.lmin*=-1;
	x.rmin*=-1;
	x.sum*=-1;
	x.max*=-1;
	x.min*=-1;
	swap(x.max,x.min);
	swap(x.lmax,x.lmin);
	swap(x.rmax,x.rmin);
}
void Pushdown(int x)
{
	if(!tr[x].cov) return;
	tr[x].cov=0;
	work(tr[x*2]);
	work(tr[x*2+1]);
	tr[x*2].cov^=1;
	tr[x*2+1].cov^=1;
}
void build(int l,int r,int x)
{
	if(l==r)
	{
		tr[x].lmax=tr[x].rmax=tr[x].max=max(0ll,a[l]);
		tr[x].lmin=tr[x].rmin=tr[x].min=min(0ll,a[l]);
		tr[x].sum=a[l];
		return ;
	}
	int mid=(l+r)/2;
	build(l,mid,x*2);
	build(mid+1,r,x*2+1);
	Pushup(x);
}
void modify(int l,int r,int L,int R,int x)
{
	if(l>R||r<L) return;
	if(l>=L&&r<=R)
	{
		tr[x].cov^=1;
		work(tr[x]);
		return ;
	}
	Pushdown(x);
	int mid=(l+r)/2;
	modify(l,mid,L,R,x*2);
	modify(mid+1,r,L,R,x*2+1);
	Pushup(x);
}
struct node{
	int l,r;
};
int get(int l,int r,int x,int v)
{
	if(l==r) return l;
	int mid=(l+r)/2;
	Pushdown(x); 
	if(v==1)
	{
		if(tr[x].rmax==tr[x*2+1].rmax) return get(mid+1,r,x*2+1,v);
		return get(l,mid,x*2,v);
	}
	else
	{
		if(tr[x].lmax==tr[x*2].lmax) return get(l,mid,x*2,v);
		return get(mid+1,r,x*2+1,v);
	}
}
node query(int l,int r,int x)
{
	if(l==r) return (node){l,l};
	int mid=(l+r)/2;
	Pushdown(x);
	if(tr[x].max==tr[x*2].max) return query(l,mid,x*2);
	if(tr[x].max==tr[x*2+1].max) return query(mid+1,r,x*2+1);
	return 	(node){get(l,mid,x*2,1),get(mid+1,r,x*2+1,0)};
}
int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x;
		ll w;
		scanf("%d%lld",&x,&w);
		a[i]=(x==1?-1:1)*w;
		sum[i]=sum[i-1]+a[i];
		if(a[i]<0) s-=a[i];
	}
	for(int i=1;i<=n;i++)
		if(sum[i]>sum[pos]) pos=i;
	s+=sum[pos];
	for(int i=1;i<=pos;i++)
		a[i]=-a[i];
	build(1,n,1);
	ans[0]=s;
	for(int i=1;i<=n;i++)
	{
		if(tr[1].max==0)
		{
			ans[i]=ans[i-1];
			continue;
		}
		node p=query(1,n,1);
		ans[i]=ans[i-1]+tr[1].max;
		modify(1,n,p.l,p.r,1);
	}
	scanf("%d",&m);
	while(m--)
	{
		int x;
		scanf("%d",&x);
		printf("%lld\n",ans[x]);
	}
	return 0;
}

T4

日常开摆

posted @ 2023-10-08 19:28  g1ove  阅读(13)  评论(0编辑  收藏  举报