ABC371 Review

ABC371 Review

A

分类讨论题 ,过

B

模拟题,过

C

题意

给出一张原始图 \(G\) ,和一张待修改图 \(H\) ,每次对 \(H\) 进行一次操作可以花费相应的代价删除已经存在的一条边或者是添加未存在的边。

问使得两张图同构的最小代价 \(W\) 是多少。

思路

以为是什么高级的算法,但是又放在了 C 这个位置,又觉得是什么高深的结论,最后还是没有做出来。

其实注意到了 \(N\) 的值非常的小,考虑过暴力 dfs ,想了想好像不知道该怎么判断选出来的图是否和原图同构,于是就放弃开 D 题去了。

后来发现其实好像不是什么比较困难的实现。并不用去在 \(H\) 图内枚举,然后又把 \(G\)\(H\) 进行对比。实际上我们最终需要的是 \(H\) 图变成 \(G\) 的模样,不如我们直接考虑对于 \(G\) 中的每一个节点,我们该把 \(H\) 中的哪一个点填进去,这一过程其实就是把 \(G\) 中的点当成下标,去 求 \(H\) 中的点的全排列,然后统计边权的和即可。

新东西

求全排列并不一定要用 dfs ,比较难写,直接用 stl 里的 next_permutation 即可,其传参原理和 sort 一致,具体格式如下:

do
{
	//work();
}while(next_permutation(a+1,a+n+1));

Code

#include<bits/stdc++.h>
using namespace std;
const int N=1e6;
template<typename T>inline void re(T &x)
{
	x=0;
	int f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	x*=f;
}
template<typename T>inline void wr(T x)
{
	if(x<0)putchar('-'),x=-x;
	if(x>9)wr(x/10);
	putchar(x%10^48);
}
struct Edge
{
	int u,v;
}g[50];
int n,mg,mh,val[15][15],inm[15][15];
int rec;
inline void pre()
{
	re(n);
	int u,v;
	re(mg);
	for(register int i=1;i<=mg;++i)
	{
		re(u),re(v);
		g[i].u=u,g[i].v=v;
	}
	re(mh);
	for(register int i=1;i<=mh;++i)
	{
		re(u),re(v);
		inm[u][v]=inm[v][u]=1;
	}
	int tmpv;
	for(register int i=1;i<=n;++i)
	{
		for(register int j=i+1;j<=n;++j)
		{
			re(tmpv);
			val[i][j]=val[j][i]=tmpv;
			if(inm[i][j])rec+=tmpv;
		}
	}
}
int a[15];
int ans=1e9+7;
inline void solve()
{
	for(register int i=1;i<=n;++i)a[i]=i;
	do
	{
		int tmpans=rec;
		for(register int i=1;i<=mg;++i)
		{
			int u=g[i].u,v=g[i].v;
			if(inm[a[u]][a[v]])tmpans-=val[a[u]][a[v]];
			else tmpans+=val[a[u]][a[v]];
		}
		ans=min(ans,tmpans);
	}while(next_permutation(a+1,a+n+1));
}
int main()
{
	pre();
	solve();
	wr(ans);
	return 0;
}

D

题意

给出 \(n\) 个二元组 \((x_i,p_i)\) ,其中 \(x_i\in[-10^9,10^9],p_i\in [0,10^9]\) 。有 \(m<2\times 10^5\) 个询问 ,每个询问给出一个 \([l,r]\) ,求出 \(\sum_i^{x_i\in[l,r]} p_i\) 的值。

思路

暴力跳显然是会超时的,然后数组的下标会出现负数,所以很自然的会想到离散化后再前缀和。

但是大可不必这么做,由于树状数组的复杂度是 \(\log n\) 的,所以完全可以直接前缀和。

这里不用开数组,直接用 unordered_map 代替数组就可以了,注意分左右两边统计。

Code

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=3e5;
template<typename T>inline void re(T &x)
{
	x=0;
	int f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	x*=f;
}
template<typename T>inline void wr(T x)
{
	if(x<0)putchar('-'),x=-x;
	if(x>9)wr(x/10);
	putchar(x%10^48);
}
int n;
int maxr=-1,minl=1e9+7;
unordered_map<int,int> c1,c2;
inline int lowbit(int x){return x&(-x);}
inline void add1(int x,int v)
{
	for(;x<=1e9;x+=lowbit(x))	
		c1[x]+=v;
}
inline int query1(int x)
{
	int ans=0;
	for(;x>=1;x-=lowbit(x))ans+=c1[x];
	return ans;
}
inline void add2(int x,int v)
{
	for(;x<=1e9;x+=lowbit(x))	
		c2[x]+=v;
}
inline int query2(int x)
{
	int ans=0;
	for(;x>=1;x-=lowbit(x))ans+=c2[x];
	return ans;
}
int x[N],p[N];
int ans0;
inline void pre()
{
	re(n);
	for(register int i=1;i<=n;++i)re(x[i]),maxr=max(maxr,x[i]),minl=min(minl,x[i]);
	minl=-minl;
	for(register int i=1;i<=n;++i)
	{
		re(p[i]);
		if(x[i]>0)
			add1(x[i],p[i]);
		else if(x[i]<0) add2(-x[i],p[i]);
		else ans0=p[i];
	}
}
signed main()
{
	pre();
	int t;
	re(t);
	while(t--)
	{
		int l,r;
		re(l),re(r);
		if(l<0)
		{
			if(r<0)
				wr(query2(-l)-query2(-r-1));
			else if (r==0)wr(query2(-l)+ans0);
			else wr(query2(-l)+ans0+query1(r));
		}
		else if(l==0)
		{
			if(r==0)wr(ans0);
			else wr(ans0+query1(r));
		}
		else
		{
			wr(query1(r)-query1(l-1));	
		}
		putchar('\n');
	}
	return 0;
}

E

题意

给定序列 \({a_N}\) ,定义 \(f(l,r)\)\([l,r]\) 区间内不同数字的种类数,求 \(\sum_{i=1}^n\sum_{j=i}^n f(i,j)\)

\(n\le2\times 10^5\)

思路

很显然,哪怕是我们能够去 \(O(n^2)\) 地统计 ,\(O(1)\) 地枚举,也是会超时的。

分开来考虑每个区间的贡献,如果一个区间包含 \(x\) ,不论个数,那么它就会对答案产生 \(1\) 的贡献。

这时候我们可以考虑转而枚举 \(x\) ,考虑在 \([1,n]\) 的范围内有多少个包含它的区间 ,那么就是 \(x\) 这个数对答案的贡献 $ans(x) $ 。

最后的答案就是 \(\sum_{x=1}^Nans(x)\)

进一步的,对于每一个 \(ans(x)\) ,都可以通过补集思想求出 ,也就是 \(ans(x)=C_{n}^2-\sum (\text{invalid})\),这一步可以在读入的时候就求出。

具体的,我们可以通过记录每一个数上一个出现的位置 \(las_{a[i]}\) ,那么对于 \(x\) 来说不合法的方案数就是 \([las_{a[i]}+1,i-1]\),注意最后要在 \(n+1\) 这个位置上面再对所有出现过的数字再用相同的方法统计一次。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6;
template<typename T>inline void re(T &x)
{
	x=0;
	int f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	x*=f;
}
template<typename T>inline void wr(T x)
{
	if(x<0)putchar('-'),x=-x;
	if(x>9)wr(x/10);
	putchar(x%10^48);
}
inline int c(int i){return i*(i-1)/2+i;}
int n,a[N],las[N];
int tmp;
signed main()
{
	cin>>n;
	int ans=0;
	int cnt=0;
	for(int i=1;i<=n;++i)
	{
		re(a[i]);
		if(!las[a[i]])cnt++;
		tmp+=c(i-las[a[i]]-1);
		las[a[i]]=i;
	}
	for(int i=1;i<=n;++i)
	{
		if(!las[i])continue;
		tmp+=c(n-las[i]);
	}
	wr(cnt*(c(n))-tmp);
//	cout<<endl;
//	for(int i=1;i<=n;++i)
	return 0;
}

延伸

对于去统计区间内不同数的种类,这道题是属于比较特殊的一种,因为会把全部的区间都给枚举完,所以我们可以从数学的角度来快速统计答案。但是如果现在不对所有区间进行询问,而是给出 \(q\)\([l,r]\) 的询问,又该如何处理呢?

很容易想到莫队等一系列区间算法,加上树状数组求逆序对的启发,大可以搞一个权值树状数组的算法,题目链接如下 :

P1972 HH的项链

这道题会单独写一篇题解。

posted @ 2024-09-15 16:27  Hanggoash  阅读(18)  评论(0)    收藏  举报
动态线条
动态线条end