五月&六月杂题选做
CF1468M
非常有意思的一道题。
将所有集合分为 \(|S_i|\le \sqrt n\) 和 \(|S_i|>\sqrt n\) 两类,成为「小集合」和「大集合」。那么答案 \((i,j)\) 存在于这三种情况中:
- \(i,j\) 都在大集合中;
- \(i\) 在大集合中,\(j\) 在小集合中;
- \(i,j\) 都在小集合中。
\(\text{Case 1 & Case 2}\) 可以一起处理,枚举大集合然后对其他集合一个一个判断即可。由于大集合数量不会超过 \(\mathcal O(\sqrt n)\),时间复杂度为 \(\mathcal O(n\sqrt n)\)。
\(\text{Case 3}\) 需要枚举答案中的 \(i\),预处理哪些集合有 \(i\),对这些集合判断是否有除 \(i\) 之外的相同元素。其流程相当于遍历了所有小集合,由于小集合的大小不会超过 \(\mathcal O(n\sqrt n)\),复杂度也为 \(\mathcal O(n\sqrt n)\)。
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
const int N=4e5+10;
vector<int> v[N],pos[N];
int k[N],t[N];
int cnt[N];
int sol()
{
int n=read(),sz=400;
for(int i=1;i<=n;i++)v[i].clear();
for(int i=1;i<=n;i++)
{
k[i]=read();
for(int j=1;j<=k[i];j++)v[i].push_back(read());
}
int zzt=0;
for(int i=1;i<=n;i++)for(int j=0;j<k[i];j++)t[++zzt]=v[i][j];
sort(t+1,t+zzt+1);zzt=unique(t+1,t+zzt+1)-t-1;
for(int i=1;i<=zzt;i++)pos[i].clear();
for(int i=1;i<=n;i++)for(int j=0;j<k[i];j++)
{
v[i][j]=lower_bound(t+1,t+zzt+1,v[i][j])-t;
if(k[i]<sz)pos[v[i][j]].push_back(i);
}
for(int i=1;i<=n;i++)
{
if(k[i]<sz)continue;
for(int j=1;j<=zzt;j++)cnt[j]=0;
for(int j=0;j<k[i];j++)cnt[v[i][j]]=1;
for(int j=1;j<=n;j++)
{
if(i==j)continue;
int c=0;
for(int l=0;l<k[j];l++)c+=cnt[v[j][l]];
if(c>=2)return printf("%d %d\n",i,j),0;
}
}
for(int i=1;i<=zzt;i++)cnt[i]=0;
for(int i=1;i<=zzt;i++)
{
for(int j=0;j<pos[i].size();j++)
{
int s=pos[i][j];
for(int l=0;l<k[s];l++)
{
if(v[s][l]==i)continue;
if(cnt[v[s][l]])return printf("%d %d\n",cnt[v[s][l]],s),0;
cnt[v[s][l]]=s;
}
}
for(int j=0;j<pos[i].size();j++)
for(int l=0;l<k[pos[i][j]];l++)
cnt[v[pos[i][j]][l]]=0;
}
printf("-1\n");
return 0;
}
int main()
{
int T;scanf("%d",&T);
while(T--)sol();
return 0;
}
CF1422D
按 \(x\) 轴排序,相邻点连边,\(y\) 轴同理。
自己感性证明吧。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
#define int long long
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e5+10,M=1e6+10;
int head[N],ver[M],nxt[M],tot=0,edge[M];
void add(int x,int y,int z)
{
// printf("add(%d, %d, %d)\n",x,y,z);
ver[++tot]=y;
edge[tot]=z;
nxt[tot]=head[x];
head[x]=tot;
}
int dis[N];bool vis[N];
void dij(int S)
{
priority_queue<pair<int,int> > que;
que.push(make_pair(0,S));
memset(dis,0x3f,sizeof(dis));
dis[S]=0;
while(!que.empty())
{
int x=que.top().second;que.pop();
if(vis[x])continue;
vis[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i],z=edge[i];
if(dis[x]+z<dis[y])
{
dis[y]=dis[x]+z;
que.push(make_pair(-dis[y],y));
}
}
}
}
struct node
{
int x,y,pos;
node(){}
}a[N];
bool cmp1(node a,node b){return a.x<b.x;}
bool cmp2(node a,node b){return a.y<b.y;}
signed main()
{
int n=read(),m=read(),sx=read(),sy=read(),tx=read(),ty=read();
for(int i=1;i<=m;i++)a[i].x=read(),a[i].y=read(),a[i].pos=i;
int S=0,T=m+1;
for(int i=1;i<=m;i++)add(S,i,min(abs(a[i].x-sx),abs(a[i].y-sy)));
for(int i=1;i<=m;i++)add(T,i,min(abs(a[i].x-tx),abs(a[i].y-ty)));
for(int i=1;i<=m;i++)add(i,S,abs(a[i].x-sx)+abs(a[i].y-sy));
for(int i=1;i<=m;i++)add(i,T,abs(a[i].x-tx)+abs(a[i].y-ty));
sort(a+1,a+m+1,cmp1);
for(int i=2;i<=m;i++)
add(a[i].pos,a[i-1].pos,min(abs(a[i].x-a[i-1].x),abs(a[i].y-a[i-1].y))),
add(a[i-1].pos,a[i].pos,min(abs(a[i].x-a[i-1].x),abs(a[i].y-a[i-1].y)));
sort(a+1,a+m+1,cmp2);
for(int i=2;i<=m;i++)
add(a[i].pos,a[i-1].pos,min(abs(a[i].x-a[i-1].x),abs(a[i].y-a[i-1].y))),
add(a[i-1].pos,a[i].pos,min(abs(a[i].x-a[i-1].x),abs(a[i].y-a[i-1].y)));
add(S,T,abs(sx-tx)+abs(sy-ty));
dij(S);
printf("%lld",dis[T]);
return 0;
}
1453E
必然是走完当前一整棵子树才能走其他子树。令 \(f(i)\) 表示节点 \(i\) 距离最近的叶子节点的距离,转移即可。
需要特判根节点,因为前驱点只需要到根节点,不需要到其他子树。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
const int N=2e5+10,M=4e5+10;
int head[N],ver[M],nxt[M],tot=0;
void add(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
int f[N],ans=0;
void dfs(int x,int fa)
{
int cnt=0,mx=0,mx1=0;
f[x]=0x7fffffff;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i];if(y==fa)continue;
cnt++;
dfs(y,x);
f[x]=min(f[x],f[y]+1);
if(f[y]+1>=mx)mx1=mx,mx=f[y]+1;
else if(f[y]+1>mx1)mx1=f[y]+1;
}
if(!cnt){f[x]=0;return;}
if(x==1)
{
if(cnt>1)ans=max(ans,max(mx,mx1+1));
else ans=max(ans,mx);
}
else if(cnt>1)ans=max(ans,mx+1);
}
void sol()
{
int n=read();
for(int i=1;i<=n;i++)head[i]=0;
tot=0;
for(int i=1;i<n;i++)
{
int u=read(),v=read();
add(u,v);add(v,u);
}
ans=0;
dfs(1,-1);
printf("%d\n",ans);
}
int main()
{
int T=read();
while(T--)sol();
return 0;
}
CF1404C
显然最优方案每次是最右边的能够删的数进行删除,能够使区级内能够删除的数都删掉。考虑怎么计算能否删除,定义:
那么前 \(i\) 个数中能可删数的个数 \(s_i\) 就有转移方程 \(s_i=s_{i-1}+[s_{i-1}\ge c_i]\)。
考虑多组询问。可以将询问离线下来按照 \(r\) 排序,同时维护 \(f_1,f_2,\cdots f_n\) 表示起点为 \(1,2,\cdots,n\),终点为 \(r\) 的 \(s_i\)。对于新来的一个 \(c_r\),找到一个位置 \(k\) 使得 \(f_1\ge f_2\ge\cdots f_k\ge c_r>f_{k+1}\ge\cdots f_n\),那么 \(c_r\) 可以对 \(f_1,f_2,\cdots,f_k\) 做 \(+1\) 的贡献。线段树维护即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
const int N=3e5+10;
struct sgt
{
struct seg
{
int l,r,add,min;
seg(){}
}t[N<<2];
void build(int p,int l,int r)
{
t[p].add=t[p].min=0;
t[p].l=l;t[p].r=r;
if(l==r)return;
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
void pd(int p)
{
if(t[p].add)
{
t[p*2].add+=t[p].add;
t[p*2+1].add+=t[p].add;
t[p*2].min+=t[p].add;
t[p*2+1].min+=t[p].add;
t[p].add=0;
}
}
void modify(int p,int l,int r,int d)
{
if(l<=t[p].l&&t[p].r<=r)
{
t[p].add+=d;
t[p].min+=d;
return;
}
pd(p);
int mid=(t[p].l+t[p].r)/2;
if(l<=mid)modify(p*2,l,r,d);
if(r>mid)modify(p*2+1,l,r,d);
t[p].min=min(t[p*2].min,t[p*2+1].min);
}
//???<x??
int query(int p,int l,int r)
{
if(l<=t[p].l&&t[p].r<=r)return t[p].min;
int ans=0x7fffffff,mid=(t[p].l+t[p].r)/2;
pd(p);
if(l<=mid)ans=min(ans,query(p*2,l,r));
if(r>mid)ans=min(ans,query(p*2+1,l,r));
return ans;
}
}T;
int a[N];
struct Query
{
int l,r,pos;
bool operator<(const Query &x)const {return r<x.r;}
}q[N];int Ans[N];
int main()
{
int n=read(),Q=read();
T.build(1,1,n);
for(int i=1;i<=n;i++)a[i]=i-read();
// for(int i=1;i<=n;i++)printf("%d ",a[i]);
for(int i=1;i<=n;i++)if(a[i]<0)a[i]=0x3f3f3f3f;
for(int i=1;i<=Q;i++)
{
q[i].l=read()+1,q[i].r=n-read();
q[i].pos=i;
}
sort(q+1,q+Q+1);
int last=0;
for(int i=1;i<=Q;i++)
{
for(int j=last+1;j<=q[i].r;j++)
{
int L=1,R=j,ans=-1;
while(L<=R)
{
int mid=(L+R)/2;
if(T.query(1,1,mid)>=a[j])ans=mid,L=mid+1;
else R=mid-1;
}
// printf("a[j]=%d, ans=%d\n",a[j],ans);
if(~ans)T.modify(1,1,ans,1);
}
// for(int i=1;i<=n;i++)printf("%d ",T.query(1,i,i));
// puts("");
last=q[i].r;
Ans[q[i].pos]=T.query(1,q[i].l,q[i].l);
}
for(int i=1;i<=Q;i++)printf("%d\n",Ans[i]);
return 0;
}
CF1399F
分治一下,\(\mathrm{calc}(x)\) 算在 \([l_x,r_x]\) 内能有多少个区间,递归计算 \(x\) 覆盖的所有子区间后就变成了带权区间覆盖问题,\(\mathcal O(n)\) 或 \(\mathcal O(n\log n)\) dp 即可。
然后我nt的以为上面这个 dp 只能 \(\mathcal O(n^2)\) 做,就歇逼了。
总的时间复杂度是 \(\mathcal O(n^2)\) 或 \(\mathcal O(n^2\log n)\),写的后者。
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
const int N=6010;
struct node
{
int l,r,pos;
node(){}
}a[N];
bool cmp(node a,node b){return a.r!=b.r?a.r<b.r:a.l>b.l;}
vector<node> v[N];
int f[N],m;
struct sgt
{
struct seg
{
int l,r,max;
seg(){}
}t[N<<2];
void build(int p,int l,int r)
{
t[p].l=l;t[p].r=r;
t[p].max=0;
if(l==r)return;
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
int query(int p,int l,int r)
{
if(l>r)return 0;
if(l<=t[p].l&&t[p].r<=r)return t[p].max;
int ans=0,mid=(t[p].l+t[p].r)/2;
if(l<=mid)ans=max(ans,query(p*2,l,r));
if(r>mid)ans=max(ans,query(p*2+1,l,r));
return ans;
}
void modify(int p,int x,int d)
{
if(t[p].l==t[p].r){t[p].max=max(t[p].max,d);return;}
int mid=(t[p].l+t[p].r)/2;
if(x<=mid)modify(p*2,x,d);
else modify(p*2+1,x,d);
t[p].max=max(t[p*2].max,t[p*2+1].max);
}
};
int w[N],n;
void calc(int x)
{
if(f[x])return;
if(v[x].empty()){f[x]=1;return;}
sgt T;T.build(1,1,m);
for(int i=1;i<=n;i++)w[i]=0;
int ans=0;
for(int i=0;i<v[x].size();i++)
{
node tmp=v[x][i];
if(!f[tmp.pos])calc(tmp.pos);
w[tmp.pos]=T.query(1,1,tmp.l-1)+f[tmp.pos];
T.modify(1,tmp.r,w[tmp.pos]);
ans=max(ans,w[tmp.pos]);
}
f[x]=ans+(x!=0);
}
int t[N];
void sol()
{
// puts("haha");
n=read(),m=0;
for(int i=0;i<=n;i++)f[i]=0;
for(int i=0;i<=n;i++)v[i].clear();
for(int i=1;i<=n;i++)t[++m]=a[i].l=read(),t[++m]=a[i].r=read(),a[i].pos=i;
// puts("haha");
sort(t+1,t+m+1);m=unique(t+1,t+m+1)-t-1;
for(int i=1;i<=n;i++)a[i].l=lower_bound(t+1,t+m+1,a[i].l)-t,a[i].r=lower_bound(t+1,t+m+1,a[i].r)-t;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(i!=j&&a[i].l<=a[j].l&&a[j].r<=a[i].r)v[i].push_back(a[j]);
for(int i=1;i<=n;i++)v[0].push_back(a[i]);
for(int i=0;i<=n;i++)sort(v[i].begin(),v[i].end(),cmp);
calc(0);
printf("%d\n",f[0]);
}
int main()
{
// freopen("1399F.in","r",stdin);
int T=read();
while(T--)sol();
return 0;
}
1268C
贪心地想,对于一个 \(k\),一定是把 \(1\sim k\) 凑到一起之后再将它们用类似冒泡排序的方式排序。排序的代价很好求,是逆序对数量,问题在于凑在一起的代价。
假设 \(1,2,\cdots,k\) 的位置分别是 \(p_1,p_2,\cdots,p_k\),那么我们就是要找到一个 \(p'\) 使得 \(\sum_{i=1}^k |p'-p_i|\) 最小。根据初一数学,必然是取中间能得到最小,可以二分这个中间值 \(p'\) 即可。\(\mathcal O(n\log^2 n)\)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
const int N=2e5+10;
int n,p[N],id[N];
struct bit
{
int c[N];
bit(){memset(c,0,sizeof(c));}
void modify(int x,int d){for(;x<=n;x+=x&-x)c[x]+=d;}
int query(int x,int ans=0){for(;x;x-=x&-x)ans+=c[x];return ans;}
}t1,t2,t3;
int Ans[N];
signed main()
{
n=read();
for(int i=1;i<=n;i++)id[p[i]=read()]=i;
for(int i=1;i<=n;i++)
{
Ans[i]+=t3.query(n)-t3.query(id[i]);
t3.modify(id[i],1);
}
for(int i=1;i<=n;i++)Ans[i]+=Ans[i-1];
for(int k=1;k<=n;k++)
{
t1.modify(id[k],1),t2.modify(id[k],id[k]);
if(k==1){printf("0 ");continue;}
int pos=0,l=1,r=n,ans=Ans[k];
while(l<=r)
{
int mid=(l+r)/2;
if(t1.query(mid)>=t1.query(n)-t1.query(mid))r=mid-1,pos=mid;
else l=mid+1;
}
int cnt1=(k+1)/2,cnt2=k/2;
int sum1=(pos-cnt1+1+pos)*cnt1/2,sum2=(pos+1+pos+cnt2)*cnt2/2;
ans+=sum1-t2.query(pos)+(t2.query(n)-t2.query(pos))-sum2;
printf("%lld ",ans);
}
return 0;
}
888G
自己做出来的第一道 2300
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
const int N=3e5+10;
typedef long long ll;
int f[N],a[N];
void init(int n){for(int i=1;i<=n;i++)f[i]=i;}
int getf(int x){return f[x]==x?x:f[x]=getf(f[x]);}
int trie[N*30][2],sz[N*30],ed[N*30],tot=0;
void ins(int x,int pos)
{
int p=0;
for(int i=29;i>=0;i--)
{
int op=(x&(1<<i))!=0;
if(!trie[p][op])trie[p][op]=++tot;
p=trie[p][op];
sz[p]++;
}
ed[p]=pos;
}
void del(int x)
{
int p=0;
for(int i=29;i>=0;i--)
{
int op=(x&(1<<i))!=0;
p=trie[p][op];
sz[p]--;
}
}
int query(int x)
{
int p=0;
for(int i=29;i>=0;i--)
{
int op=(x&(1<<i))!=0;
if(trie[p][op]&&sz[trie[p][op]])p=trie[p][op];
else p=trie[p][op^1];
}
return ed[p];
}
int n;ll ans=0;
vector<int> v[N];
struct node
{
int u,v,w;
bool operator <(const node &x)const {return w<x.w;}
};
void sol()
{
int cnt=0;
for(int i=1;i<=n;i++)cnt+=getf(i)==i;
if(cnt==1)return;
for(int i=1;i<=n;i++)v[i].clear();
for(int i=1;i<=n;i++)v[getf(i)].push_back(i);
vector<node> s;
for(int i=1;i<=n;i++)
{
if(v[i].empty())continue;
int Min=0x7fffffff,pos=0,pos1=0;
for(int j=0;j<v[i].size();j++)
{
int x=v[i][j];
del(a[x]);
}
for(int j=0;j<v[i].size();j++)
{
int x=v[i][j],tmp=query(a[x]);
if((a[x]^a[tmp])<Min)Min=(a[x]^a[tmp]),pos=x,pos1=tmp;
}
s.push_back((node){pos,pos1,Min});
for(int j=0;j<v[i].size();j++)
{
int x=v[i][j];
ins(a[x],x);
}
}
sort(s.begin(),s.end());
for(int i=0;i<s.size();i++)
{
int u=s[i].u,v=s[i].v,w=s[i].w;
if(getf(u)!=getf(v))ans+=w,f[getf(v)]=getf(u);
}
sol();
}
signed main()
{
n=read();init(n);
for(int i=1;i<=n;i++)a[i]=read();
sort(a+1,a+n+1);
n=unique(a+1,a+n+1)-a-1;
for(int i=1;i<=n;i++)ins(a[i],i);
sol();
printf("%lld",ans);
return 0;
}
Codeforces 1251E1&E2
非常nb的一道题。
有一个巧妙的转化:将投票转化成给这 \(n\) 个人排一个顺序,若排在第 \(i\) 个位置的人的 \(m\) 满足 \(m<i\),那么这个人没有代价,反之需要花费 \(p_i\) 的代价,求最小代价。反过来想,就是让满足 \(m<i\) 的所有人排得尽量前,那么用一个 priority_queue 维护即可。
#include<iostream>
#include<queue>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
typedef long long ll;
const int N=2e5+10;
struct node{int m,c;}a[N];
bool cmp(node a,node b){return a.m<b.m;}
void sol()
{
int n=read();
ll ans=0;
for(int i=1;i<=n;i++)a[i].m=read(),ans+=(a[i].c=read());
sort(a+1,a+n+1,cmp);
priority_queue<int> que;
for(int i=1,j=1;i<=n;i++)
{
while(j<=n&&a[j].m<i)que.push(a[j++].c);
if(!que.empty())ans-=que.top(),que.pop();
}
printf("%lld\n",ans);
}
int main()
{
int T=read();
while(T--)sol();
return 0;
}