ZR CSP7连 D5

这场算是我打得第二好的一场吧……排名 92,Rating 涨了三十几,得分是 100+90+50+15=255。

闲话不多说,直奔主题。

T1

题意简述
  • 输入一行字符串 S S S(由字母、数字、空格组成),在每一组相邻且没有间隔空格的字母和数字之间加入空格再输出。
  • ∣ S ∣ ≤ 100 |S|\leq100 S100
题目解析

大水题,直接用 isdigit() 函数和 isalpha() 函数判断一下即可,出题人说出此题的原因是他参赛时的提高组的 T1 基本都要涉及到字符串(??),想让我们学一下 getline() 函数等……

代码
#include<bits/stdc++.h>
using namespace std;
string x;
int main()
{
	getline(cin,x);
	printf("%c",x[0]);
	for(int i=1;i<x.size();i++)
	{
		if((isalpha(x[i])&&isdigit(x[i-1]))||(isdigit(x[i])&&isalpha(x[i-1])))
			printf(" ");
		printf("%c",x[i]);
	}
	return 0;
}

T2

题意简述

n n n 个数,第 i i i 个数为 a i a_i ai (可能为负)。做一个双人游戏,分先后手,每次先手可以取任一个数,而后手只可取一个数,先后手交替取直到数都被取完。现在先手想要自己取的数之和最大,后手则希望先手取的数之和最小。求双方都使用最优策略的情况下先手取的数之和。

题目解析

首先很显然第一次取的时候先手取完所有的 a i > 0 a_i>0 ai>0 是最优的,接下来是对 a i < 0 a_i<0 ai<0 的处理。

明显先手取的负数之和需要最大,且后手的最优策略也是取当前最大的负数,所以按最优策略,在先手第一次取之后,后先手交替取当前最大的负数。但是注意到先手第一次取数时可以另外取最大的 T T T 个负数,那么我们会发现 T ≥ 2 T\geq 2 T2 时明显比 T < 2 T<2 T<2 要劣。也就是说,最优的情况下, T = 0 T=0 T=0 1 1 1。因此我们就可以分别判断这两种情况取 max ⁡ \max max 即可。

但是注意一个特例:如果 a i a_i ai 都是负数,第一步先手由于一定要取数,所以 T = 1 T=1 T=1 2 2 2。(我赛场上失掉的 10pts 就是这么来的)

代码
#include<bits/stdc++.h>
using namespace std;
int n,cnt;
long long a,b[100010],sum1,sum2;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a);
		if(a>=0)
			sum1+=a;
		else
			b[++cnt]=a;
	}
	sum2=sum1;//先取完所有正数
	sort(b+1,b+cnt+1,greater<long long>());//排序
	for(int i=1;i<=cnt;i++)
		if(i&1)
			sum1+=b[i];//T=1
		else
			sum2+=b[i];//T=0
	printf("%lld",max(sum1,sum2+b[cnt==n]/*如果都是负数,先手需要先取最大负数*/));
	return 0;
}

T3

题意简述

一个长度为 n n n 的整数数组 a a a,其中 a i ∈ [ 1 , 100 ] a_i\in[1,100] ai[1,100],求出平均数为整数的 a a a 的连续段的个数。

题目解析

赛场上就是直接暴力枚举子区间,50pts 滚粗。接下来说一下正解。

显然 a a a 的连续段的平均数 k ∈ [ min ⁡ { a 1 , a 2 , ⋯   , a n − 1 , a n } , max ⁡ { a 1 , a 2 , ⋯   , a n − 1 , a n } ] k\in[\min\{a_1,a_2,\cdots,a_{n-1},a_n\},\max\{a_1,a_2,\cdots,a_{n-1},a_n\}] k[min{a1,a2,,an1,an},max{a1,a2,,an1,an}],基于 a i a_i ai 的范围很小,可以直接枚举平均数 k k k,然后所做的就是统计 a a a 中有多少连续段的平均数为 k k k。那么我们只需要计算出:

s i = ∑ i = 1 n a i − k s_i=\sum_{i=1}^n a_i-k si=i=1naik

然后我们会发现对于 x , y ∈ [ 1 , n ] x,y\in[1,n] x,y[1,n],如果 s x = s y s_x=s_y sx=sy,那么在原序列 a a a 中的 a x , a x + 1 , ⋯   , a y − 1 , a y a_x,a_{x+1},\cdots,a_{y-1},a_y ax,ax+1,,ay1,ay 一段的平均数为 k k k

这样就很好处理了,只需要用一个哈希表维护 s i s_i si 里面的数,然后对于每个数的个数 c n t cnt cnt,计算出 C c n t 2 C_{cnt}^2 Ccnt2 并累加到答案里即可。

代码
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
int n,a[100010],mn=INT_MAX,mx=INT_MIN,s[100010];
long long ans;
gp_hash_table<int,int>h;//探查法比 unordered_map 和 cc_hash_table 的拉链法的效率高很多。
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]),mn=min(mn,a[i]),mx=max(mx,a[i]);
	for(int k=mn;k<=mx;k++)
	{
		h.clear();
		for(int i=1;i<=n;i++)
			s[i]=s[i-1]+a[i]-k;
		h[0]++;
		for(int i=1;i<=n;i++)
			h[s[i]]++;
		for(auto x:h)
			ans+=(long long)x.second*(x.second-1ll)/2ll;
	}
	printf("%lld",ans);
	return 0;
}

T4

题意简述

给出 n , m , l , q n,m,l,q n,m,l,q 以及 q q q 条形如 ( x i , y i ) (x_i,y_i) (xi,yi) 的规定。意义是一张 n n n 个顶点、 m m m 条边的有向图,要求有 l l l 个强连通分量,同时要求对于 ∀ i , 1 ≤ i ≤ q \forall i,1\le i\le q i,1iq,有 x i x_i xi y i y_i yi 在同一个强连通分量中。求构造一个满足条件的有向图,若无解输出 -1

题目解析

赛场上直接输出 -1,15pts 到手。正解如下:

用并查集维护给出的规定,每次将 x i x_i xi y i y_i yi 合并到同一个代表强连通分量的等价类 s 1 , s 2 , s 3 , ⋯   , s k s_1,s_2,s_3,\cdots,s_k s1,s2,s3,,sk,若 k < l k<l k<l,则无解,否则按照 ∣ s ∣ |s| s 从小到大排序后合并 s l ⋯ s k s_l\cdots s_k slsk(合并最大的是为了计算最大边数)。

接着计算最小最大边数判断是否有解,对于每一个等价类 s s s,如果 ∣ s ∣ = 1 |s|=1 s=1,则最小最大边数都为 0 0 0,否则最小边数为 ∣ s ∣ |s| s(环),最大边数为 ∣ s ∣ × ( ∣ s ∣ − 1 ) |s|\times(|s|-1) s×(s1)(完全图);对于两个强连通分量之间的边,由于不能由它们以及对应强连通分量内的边组成一个新的强连通分量,所以我们假设只能从编号小的等价类向编号大的连边,则对于 s i s_i si s j s_j sj 之间,最小边数为 0 0 0,最大边数是 ∣ s i ∣ × ∣ s j ∣ |s_i|\times|s_j| si×sj。注意此处如果枚举 s i s_i si s j s_j sj 则时间复杂度为 O ( l 2 ) O(l^2) O(l2),无法接受。因此需要更高效的方式:累加。注意有:

∑ i = 1 l ∑ j = i + 1 l ∣ S i ∣ × ∣ S j ∣ = ∑ i = 1 l ∑ j = 1 i − 1 ∣ S i ∣ × ∣ S j ∣ = ∑ i = 1 l ( ∣ S i ∣ × ∑ j = 1 i − 1 ∣ S j ∣ ) \sum_{i=1}^l \sum_{j=i+1}^l |S_i|\times|S_j|=\sum_{i=1}^l \sum_{j=1}^{i-1} |S_i|\times|S_j|=\sum_{i=1}^l (|S_i|\times\sum_{j=1}^{i-1} |S_j|) i=1lj=i+1lSi×Sj=i=1lj=1i1Si×Sj=i=1l(Si×j=1i1Sj)

也就是说外层的 i i i 变为 i + 1 i+1 i+1 时, j j j 也只会多一个新决策,那么我们只需要维护一个 s u m sum sum,,每次累加上新的 ∣ S j ∣ |S_j| Sj 即可,此时复杂度为 O ( l ) O(l) O(l)

然后将 m m m 与最大最小边数比较并判断是否无解。如果有解,我们来思考一下构造方法:首先对于每个强连通分量先输出其构成的环保障强连通性,接着根据 m m m 的大小不断在强连通分量内连接不在环上的边,并从编号小的强连通分量里的点不断向编号大的强连通分量里的点连边即可。

最后分析一下时间复杂度。生成等价类的过程的复杂度为 O ( q ) O(q) O(q),排序复杂度为 O ( k log ⁡ k ) = O ( n log ⁡ n ) O(k\log k)=O(n\log n) O(klogk)=O(nlogn),合并复杂度为 O ( k − l ) = O ( n − l ) O(k-l)=O(n-l) O(kl)=O(nl),计算最大最小边数的时间复杂度为 O ( l ) O(l) O(l),输出边的复杂度为 O ( m ) O(m) O(m),因此总时间复杂度为 O ( q + n log ⁡ n + m ) O(q+n\log n+m) O(q+nlogn+m)

代码
#include<bits/stdc++.h>
using namespace std;
int n,l,q,x,y,fa[500010],sy[500010],k,mn;
long long m,mx,s;
vector<int>bh[500010];//等价类
int getf(int x)
{
	return fa[x]==x?x:fa[x]=getf(fa[x]);
}
bool cmp(vector<int>a,vector<int>b)
{
	return a.size()<b.size();
}
int main()
{
	scanf("%d%lld%d%d",&n,&m,&l,&q);
	for(int i=1;i<=n;i++)
		fa[i]=i;//并查集初始化
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&x,&y);
		int a=getf(x),b=getf(y);
		if(a!=b)
			fa[a]=b;//并查集合并
	}
	for(int i=1;i<=n;i++)
		if(fa[i]==i)
			sy[i]=++k;
	if(k<l)
	{
		printf("-1");
		return 0;
	}
	for(int i=1;i<=n;i++)
		bh[sy[getf(i)]].push_back(i);
	sort(bh+1,bh+k+1,cmp);//排序
	for(int i=l+1;i<=k;i++)
		for(auto x:bh[i])
			bh[l].push_back(x);
	for(int i=1;i<=l;i++)
		mn+=bh[i].size()*(bh[i].size()>1),mx+=(long long)bh[i].size()*(bh[i].size()+s-1),s+=(long long)bh[i].size();//我这里为了方便把计算所有边数的部分合并了,s 就是用来累加的。
	if(m<(long long)mn||m>mx)
	{
		printf("-1");
		return 0;
	}
	for(int i=1;i<=l&&m;i++)
	{
		if(bh[i].size()==1)
			continue;
		for(int j=1;j<bh[i].size()&&m;j++,m--)
	        printf("%d %d\n",bh[i][j-1],bh[i][j]);
	    printf("%d %d\n",bh[i][bh[i].size()-1],bh[i][0]);
	    m--;
	}//先输出一个环
	for(int i=1;i<=l&&m;i++)
		for(int x=0;x<bh[i].size()&&m;x++)
			for(int j=i;j<=l&&m;j++)
				for(int y=0;y<bh[j].size()&&m;y++)		    
					if(i!=j||(y!=x&&y!=x+1&&(x!=bh[i].size()-1||y!=0)))
		    			printf("%d %d\n",bh[i][x],bh[j][y]),m--;//再输出其他的合法边
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值