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
日常开摆