atcoder350,351,352,353,354,355期部分题解

声明:有些题感觉已经说到很明白了,就先不写代码了,有空会补上

目录

350D: new friend

350E: toward 0

351D:Grid and Magnet

352D:permutation  subsequence

353C: sigma problem

353D: another sigma problem

354C: atcoder magics

355C: bingo2

355D: intersecting intervals 

附加题:火烧赤壁


希望读者可以有所收获 

        

        

350D: new friend

就是问你,最终有多少对这种关系:x和y是朋友,y和z是朋友,但是x和z不是朋友。你也把这种关系理解成y是中介方,但是我们要明白一点,x和y和z一定是在同一个联通块中。所以选点一定就是在联通块中选择。

也就是说:在同一个联通块中,如果x和y是朋友关系,那么它们不满足题意;如果x和y不是朋友关系,那么它们就满足题意。所以我们只需要找所有联通块中有多少对新朋友,加起来,然后减去原来的朋友数即可。

(为什么我会想到联通块呢?因为在我们做题的时候,尤其是图论,一定要尽量把题给抽象一下,你才能想到考察的知识点)

最后,要求联通块中的新朋友对数,就需要求联通块中的点数,然后拿总数减去不合法的数量就是答案。我们可以dfs跑联通块,然后返回这个联通块中的点数,同时给遍历过的点打上标签,然后去跑别的联通块;我们也可以直接并查集来做,维护祖宗节点的cnt值,存放该集合的点数。

        

        

350E: toward 0

题意:给一个N,然后每轮有两种操作,一个是花费x变成[N/A],另一个是花费y变成[N/b],b等概率取1,2,3,4,5,6。 求变成0的花最小花费。

分析:一道期望dp,感觉和概率dp差不多,大致思路都是dp[起始状态]=初始概率或者初始期望,然后找每轮之间的关系进行转移,一直dp到最终状态就行了。

我们先找每轮之间的关系:

我们设置f[N]表示N变成0的期望花费,初始f[0]=0

f[N]=Y+(f[N/6]+f[N/5]+f[N/4]+f[N/3]+f[N/2]+f[N/1])/6

修改一下变成:f[N]=6/5*Y+(f[N/6]+f[N/5]+f[N/4]+f[N/3]+f[N/2])/5

所以:f[N]=min(X+f[N/A],6/5*Y+(f[N/6]+f[N/5]+f[N/4]+f[N/3]+f[N/2])/5)

接下来借助dfs转移就行了,不过因为数据量比较大,所以要进行记忆化,所以对<ll和double>进行映射,那么就是map的事情了。ll指的是f[N]中的N,double指的是花费

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
unordered_map<ll,double>mp;
ll N,A,X,Y;
double f(ll x){
	if(x==0)return 0; //初始值
	if(mp.count(x))return mp[x]; //记忆化
	double ret2=0,ret1=f(x/A)+X;//double存储f[x]的值
	for(int i=2;i<=6;i++)ret2+=f(x/i);
	ret2=(ret2+6*Y)/5;
	return mp[x]=min(ret1,ret2);//返回f[x]
}
int main(){
	cin>>N>>A>>X>>Y;
	printf("%.9f",f(N));
}

        

        

351D:Grid and Magnet

 题意:HxW的网格,' . '代表可以走,' # '代表有磁铁,会吸住上下左右的格子,导致不能再移动,要求输出从某一个点能走的最多格子。

考的还是联通块,我们可以把每个磁铁周围会被吸住的格子打上标记,然后我们去搜图中的每一个联通块,并统计每个联通块的块数,最后输出最大的块数即可。

        

        

352D:permutation  subsequence

题意:有一个序列1~n的某一排序,我们要在此排序中找到长度最小的包含长度为k的任意自然数段。

我这里强调一下这种自然数某一排序题的常见思路:就是分析pos数组,pos数组就是每个数的位置信息。

比如:10 1 6 8 7 2 5 9 3 4   构造pos数组如下:

数组:2 6 9 10 7 3 5 4 8 1(表示每个1~n的位置信息)

我们开始考虑1~5的最小符合题意的长度:处理2 6 9 10 7即可,max-min=8,说明长度为8

考虑2~6:max-min=7

考虑3~7:max-min=7

考虑4~8:max-min=5

考虑5~9:max-min=5

考虑6~10:max-min=7

所以答案就是5

也就是我们只需要求出每个区间的极差即可,使用滑动窗口就可以了。

        

        

353C: sigma problem

首先要理解这个玩意:

它意思就是

    for(int i=1;i<=n-1;i++)
	for(int j=i+1;j<=n;j++){
		ans+=f(a[i],a[j]);
	}

但是我们肯定不能直接这样做,因为太慢了,那么我们就要找规律了:

那么最直接的想法就是“变加为乘”,以此来加速。

我们不难发现:全部的相加过程中:

a[1]+a[2]   a[1]+a[3]   a[1]+a[4]……a[1]+a[n]

a[2]+a[3]   a[2]+a[4]……a[2]+a[n]

……

也就是说如果我们先不考虑取模的情况时,每个数a[i]都会被加n-1次

然后我们再考虑取模的情况:

这里如果没有发现a[i]<10^8这个细节的话,估计就很难做出来了,如何使用这个条件呢?

我们会发现a[i]+a[j]在mod10^8的过程中最多也就是减去了一次10^8,所以我们在把所有的a[i]+a[j]大于10^8的个数找出来,然后减去10^8就行了。

#include <bits/stdc++.h>
using namespace std;
int a[300000];
int mod=100000000;
int main(){
	int n;cin>>n;
	long long ans=0;
	for(int i=0;i<n;i++){
		cin>>a[i];ans+=a[i]*(n-1ll);
	}
	sort(a,a+n);
	for(int i=0;i<n;i++){
		ans-=mod*1ll*(a+n-lower_bound(a+1+i,a+n,mod-a[i]));//另外一个数一定比当前的数更大,所以从a+1+i开始找
	}
	cout<<ans<<'\n';
}

        

        

353D: another sigma problem

这个还是要“变加为乘”,但是要注意的是,这个题不可以进行排序,因为f(x,y)不等于f(y,x),所以我们要顺序来。对于f(a[i],a[j]),a[j]本身就会加一次,a[i]乘一下a[j]的倍率。

首先我们可以发现,每个数都会加上n-1遍,所以这些贡献可以单独求出来。

剩下的倍率问题:对于a[i],我们可以求它后面数每个数的位数,然后求出每个倍率然后加起来

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll mod=998244353;
ll suf[200005],a[200005];
ll get(ll x){
	ll ans=1;
	while(x)x/=10,ans*=10;
	return ans;
}
int main(){
	int n;cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	ll ans=0;
	for(int i=n;i>=1;i--){
		suf[i]=(suf[i+1]+get(a[i]))%mod; //每个数要乘的倍率的和(后缀和)
	}
	for(int i=1;i<=n;i++){
		ans+=a[i]*(i-1)%mod;//前半部分的贡献
		ans%=mod;
		ans+=suf[i+1]*a[i]%mod;//后半部分的贡献
		ans%=mod;
	}
	cout<<ans<<'\n';
}

        

        

354C: atcoder magics

题意:有N张卡片,每张卡片上有A和C两个数,分别表示力量和花费,我们可以进行操作:

选择两个卡片,如果A1>A2,C1<C2,我们就丢掉2卡片。问我们最终可以剩下哪些卡片?

做这种题的一个直觉就是排序:如果我们按照A从大到小排好序后,就不用再考虑A了,只需要考虑C。如果当前的卡片C值比前面的卡片的C值都小,那就留下他,否则就丢掉这个卡片。

#include <bits/stdc++.h>
using namespace std;
struct card{
	int val,cost,id;
}a[200005];
bool cmp(card x,card y){
	return x.val>y.val;
}
int main(){
	int n;cin>>n;
	for(int i=1;i<=n;i++){
		int val,cost;
		cin>>val>>cost;
		a[i]={val,cost,i};
	}
	sort(a+1,a+1+n,cmp);
	int minn=2e9;
	vector<int> ans;
	for(int i=1;i<=n;i++){
		if(a[i].cost<minn){
			ans.push_back(a[i].id);
			minn=a[i].cost;
		}
	}
	sort(ans.begin(),ans.end());
	cout<<ans.size()<<'\n';
	for(auto i:ans){
		cout<<i<<' ';
	}
	return 0;
}

        

        

355C: bingo2

题意:有一个N*N的网格,依次填入1,2,3,4……n^2(如图),然后一共有T轮,每轮都会填一个数,问至少几轮后才有一列或者一行,或者一个对角线被填充?

考察的就是如何用vis表示一个对角线,不会的回去看看CSDN中的八皇后问题,填入一个就vis++,然后检查对应的行,列,对角线,看看什么时候可以满足题意。

        

        

355D: intersecting intervals 

题意:有n个区间,问你一共有多少对区间相交?

先拆开分析:

1开始 7开始 3开始

5结束 8结束 7结束

我们排序:

1开始

3开始

5结束

7开始

7结束

8结束

我们要统计每个区间开始出现的时候,已经有多少个区间存在了,那么此时就会增加多少个相交区间,也就是我们要关心目前有多少个区间往后面走,当开始出现一个区间时候cnt++,当开始减少一个区间时候cnt--

排序的时候注意相同时间开始在前,结束在后(因为在某一点都同时有开始和结束也算相交)

1开始  cnt=1

3开始  cnt=2  ans=1

5结束  cnt=1  

7开始  cnt=2  ans=2

7结束  cnt=1

8结束  cnt=0

再来一个样例

1开始  cnt=1

2开始  cnt=2  ans=1

3开始  cnt=3  ans=3

4结束  cnt=2

5结束  cnt=1

6结束  cnt=0

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node{
	int v,op; //op=1为结束,op=0为开始
};
vector<node>ve;
bool cmp(node x,node y){
	if(x.v==y.v)return x.op<y.op;//先返回起点再返回终点
	return x.v<y.v;
}
int main(){
	int n;cin>>n;
	for(int i=1;i<=n;i++){
		int l,r;cin>>l>>r;
		ve.push_back({l,0});
		ve.push_back({r,1});
	}
	sort(ve.begin(),ve.end(),cmp);
	int cnt=0;ll ans=0;
	for(int i=0;i<ve.size();i++){
		int v=ve[i].v,op=ve[i].op;
		if(op==0)ans+=cnt,cnt++;
		else cnt--;
	}
	cout<<ans<<'\n';
	return 0;
}

看完这题,建议再看一道很像的题。

        

        

附加题:火烧赤壁

模拟就行,首先按照起点递增排序,终点递增排序。维护一个end表示当前烧的最右边界,然后遍历下一个区间,看看下个区间的起点是否能和end连接上,如果不能就更新起点和终点,如果能就判断下个区间是否被包括,如果没有被包括,即end值更大就更新end 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e4+10;
ll ans;
int n;
struct node{ll x;ll y;}e[N];
bool cmp(node a,node b){
	if(a.x==b.x)return a.y<b.y;
	return a.x<b.x; 
}
int main(){
	cin>>n;ll a,b;
	for(int i=1;i<=n;i++){
		scanf("%lld%lld",&a,&b);
		e[i].x=a;e[i].y=b-1;
	}
	sort(e+1,e+1+n,cmp);
	ll end=e[1].y;
	ans+=e[1].y-e[1].x+1;
	for(int i=2;i<=n;i++){
		if(end>=e[i].x){
			if(end>=e[i].y)continue;
			ans+=e[i].y-end;
			end=e[i].y;
		}
		else {
			ans+=e[i].y-e[i].x+1;
			end=e[i].y;	
		}
	}
	cout<<ans;
}

  • 28
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值