五月&六月杂题选做

CF1468M

Link

非常有意思的一道题。

将所有集合分为 \(|S_i|\le \sqrt n\)\(|S_i|>\sqrt n\) 两类,成为「小集合」和「大集合」。那么答案 \((i,j)\) 存在于这三种情况中:

  1. \(i,j\) 都在大集合中;
  2. \(i\) 在大集合中,\(j\) 在小集合中;
  3. \(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

Link

\(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

显然最优方案每次是最右边的能够删的数进行删除,能够使区级内能够删除的数都删掉。考虑怎么计算能否删除,定义:

\[c_i=\begin{cases}i-a_i, &i-a_i\ge 0\\+\infty, &i-a_i<0\end{cases} \]

那么前 \(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

Link

分治一下,\(\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

Link

贪心地想,对于一个 \(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

Link

自己做出来的第一道 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;
}
posted @ 2021-05-21 15:49  zzt1208  阅读(73)  评论(0编辑  收藏  举报