声明:有些题感觉已经说到很明白了,就先不写代码了,有空会补上
目录
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。
分析:一道期望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来完成转移更加方便。
#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;
for(int i=2;i<=6;i++)ret2+=f(x/i);
ret2=(ret2+6*Y)/5;
return mp[x]=min(ret1,ret2);
}
int main(){
cin>>N>>A>>X>>Y;
printf("%.9f",f(N));
}
代码为什么要用double呢?因为存在性质:[[N/a]/b]=[N/ab],所以要保持精度。
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
#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;
}
看完这题,建议再看一道很像的题。
附加题:火烧赤壁
#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;
}