Hello 2020 题解
Posted 1000suns
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hello 2020 题解相关的知识,希望对你有一定的参考价值。
新年第一场。
开场 2min 写了 A,再 5min 写了 B,再 4min 写了 C。
然后由于脑子一抽,没想清楚就开始码 D,花了 18min 写完。
然后发现好友列表里的人好像切四题的不多,还能玩。
然后开始想 E。
一开始先去问了个是否必须是凸多边形和一个组成凹多边形(会有三种情况)的点集要被算多少次。
看到三种情况只能算一次,心态就崩了……
5min 后才发现如果合法,恰好一种是合法的,白白浪费了一堆时间……
然后开码。比想象中的好码。但由于计算几何,所以还是用了很长时间,大概 1h。
然后 WA9。
对拍也不太想写,就静态查错,死都查不出来。
弃疗了,水群去了。
……
然后天真的以为 long double 就够了,又送一发罚时。
最后还是重构成了叉积+判象限的写法,终于在 2:09 过掉了。
E 的得分比 C 还少。
看起来发挥还不错,上 IM 了。
(戏剧性的一幕:tourist 最后 2min 切了 G 翻上了 rk1,predictor 显示 dls 将仍是榜二,然后 tourist 的 G FST 了)
(祝贺 dls 登基,今年是 MiFaFaOvO 元年)
A
入门模拟。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=22;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,m,q;
char s[maxn][maxn],t[maxn][maxn];
int main(){
n=read();m=read();
FOR(i,0,n-1) scanf("%s",s[i]+1);
FOR(i,0,m-1) scanf("%s",t[i]+1);
q=read();
while(q--){
int y=read()-1;
printf("%s%s
",s[y%n]+1,t[y%m]+1);
}
}
B
怎么跟我自己出的一题有点像……
反过来计算不合法的方案数,也就是不升序列的个数。
考虑选了 (i,j) 拼在一起,那么要 (i,j) 都是不升序列,而且 (last_ige first_j)。
把不升序列拎出来,枚举 (i),计算 (le last_i) 的 (first_j) 有多少个。可以前缀和或者二分。
时间复杂度 (O(nlog n)) 或 (O(n+max(a)))。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=1000100;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,l,a[maxn],s[maxn],t[maxn],m,cnt[maxn];
ll ans;
int main(){
n=read();
FOR(i,1,n){
l=read();
FOR(j,1,l) a[j]=read();
bool flag=false;
FOR(j,2,l) flag|=a[j-1]<a[j];
if(!flag) cnt[a[1]]++,t[++m]=a[l];
}
FOR(i,1,1000000) cnt[i]+=cnt[i-1];
ans=1ll*n*n;
FOR(i,1,m) ans-=cnt[t[i]];
printf("%lld
",ans);
}
C
考虑 ([l,r]) 作为连续段出现的次数。
这些数可以随便排,((r-l+1)!)。
剩下的数可以随便排,((n-(r-l+1))!)。
选择连续段的开始位置,(n-(r-l+1)+1)。
发现对于长度相同的 ([l,r]) 贡献一样。枚举长度,答案就是 (displaystylesum_{i=1}^ni!(n-i)!(n-i+1)^2)。
时间复杂度 (O(n))。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=250025;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,mod,ans,fac[maxn];
int main(){
n=read();mod=read();
fac[0]=1;
FOR(i,1,n) fac[i]=1ll*fac[i-1]*i%mod;
FOR(i,1,n) ans=(ans+1ll*fac[n-i]*fac[i]%mod*(n-i+1)%mod*(n-i+1))%mod;
printf("%d
",ans);
}
D
发现只需要考虑大小为 (2) 的子集。
因为如果存在大小为 (2) 的子集不满足条件,那就是不满足条件;如果全部大小为 (2) 的子集都满足条件,那么很明显整个也满足条件。
问题就变成:对于任意两个(不知道什么东西),要么它们的两种区间同时有交,要么同时没有交。
接下来大概有一万种做法了。
我的做法:对于 ( exttt{A}) 区间没有交的东西,判断 ( exttt{B}) 区间有没有交;对于 ( exttt{B}) 区间没有交的东西,判断 ( exttt{A}) 区间有没有交。
下面就只说第一个了。
按 ( exttt{A}) 区间左端点从小到大排序。枚举第一个东西,考虑 ( exttt{A}) 区间右端点小于这个枚举的 ( exttt{A}) 区间的左端点的所有东西,都能作为第二个东西。
判断 ( exttt{B}) 区间有没有交,可以对每个第二个东西的 ( exttt{B}) 区间做区间加,然后判断第一个东西的 ( exttt{B}) 的区间和是否为 (0)。
时间复杂度 (O(nlog n))。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=222222;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
struct seg{
int la,ra,lb,rb;
}s1[maxn],s2[maxn];
int n,la[maxn],ra[maxn],lb[maxn],rb[maxn],tmpa[maxn],tla,tmpb[maxn],tlb;
ll b1[maxn],b2[maxn];
inline bool cmpla(const seg &x,const seg &y){return x.la<y.la;}
inline bool cmpra(const seg &x,const seg &y){return x.ra<y.ra;}
inline bool cmplb(const seg &x,const seg &y){return x.lb<y.lb;}
inline bool cmprb(const seg &x,const seg &y){return x.rb<y.rb;}
inline void update(ll *bit,int p,ll v){
for(int i=p;i<=max(tla,tlb);i+=i&-i) bit[i]+=v;
}
inline ll query(ll *bit,int p){
ll s=0;
for(int i=p;i;i-=i&-i) s+=bit[i];
return s;
}
inline void update(int p,ll v){
update(b1,p,v);
update(b2,p,1ll*p*v);
}
inline ll query(int p){
return 1ll*(p+1)*query(b1,p)-query(b2,p);
}
inline void update(int l,int r,ll v){
update(l,v);update(r+1,-v);
}
inline ll query(int l,int r){
return query(r)-query(l-1);
}
int main(){
n=read();
FOR(i,1,n){
la[i]=read();ra[i]=read();lb[i]=read();rb[i]=read();
tmpa[++tla]=la[i];tmpa[++tla]=ra[i];
tmpb[++tlb]=lb[i];tmpb[++tlb]=rb[i];
}
sort(tmpa+1,tmpa+tla+1);tla=unique(tmpa+1,tmpa+tla+1)-tmpa-1;
sort(tmpb+1,tmpb+tlb+1);tlb=unique(tmpb+1,tmpb+tlb+1)-tmpb-1;
FOR(i,1,n){
la[i]=lower_bound(tmpa+1,tmpa+tla+1,la[i])-tmpa;
ra[i]=lower_bound(tmpa+1,tmpa+tla+1,ra[i])-tmpa;
lb[i]=lower_bound(tmpb+1,tmpb+tlb+1,lb[i])-tmpb;
rb[i]=lower_bound(tmpb+1,tmpb+tlb+1,rb[i])-tmpb;
s1[i]=s2[i]=(seg){la[i],ra[i],lb[i],rb[i]};
}
sort(s1+1,s1+n+1,cmpla);
sort(s2+1,s2+n+1,cmpra);
int cur=1;
FOR(i,1,n){
while(cur<=n && s2[cur].ra<s1[i].la){
update(s2[cur].lb,s2[cur].rb,1);
cur++;
}
if(query(s1[i].lb,s1[i].rb)) return puts("NO"),0;
}
MEM(b1,0);MEM(b2,0);
sort(s1+1,s1+n+1,cmplb);
sort(s2+1,s2+n+1,cmprb);
cur=1;
FOR(i,1,n){
while(cur<=n && s2[cur].rb<s1[i].lb){
update(s2[cur].la,s2[cur].ra,1);
cur++;
}
if(query(s1[i].la,s1[i].ra)) return puts("NO"),0;
}
puts("YES");
}
E
计算每个点 (p) 的贡献。发现就是在剩下的点中选四个,能全在经过 (p) 的某一条直线的一侧的就不满足条件。
考虑从反面计算,用总方案数(在剩下的点中选四个),减去能全在经过 (p) 的某一条直线的一侧的。
把 (p) 看成原点,剩下的点极角序排序。
枚举最逆时针(应该懂什么意思)的点,剩下三个点应该能在一个区间中随意选择。计算这个区间是可以用双指针的。
时间复杂度 (O(n^2log n))。
注意,别用 atan2
,最好用叉积判断。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=5050;
const long double pi=3.141592653589793238;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
inline int at(int x,int y){
if(x>=0 && y>0) return 0;
if(x<0 && y>=0) return 1;
if(x<=0 && y<0) return 2;
if(x>0 && y<=0) return 3;
return -1;
}
struct point{
int x,y;
point(int xx=0,int yy=0):x(xx),y(yy){}
point operator+(const point &p)const{return point(x+p.x,y+p.y);}
point operator-(const point &p)const{return point(x-p.x,y-p.y);}
point operator-()const{return point(-x,-y);}
ll operator*(const point &p)const{return 1ll*x*p.y-1ll*y*p.x;}
bool operator<(const point &p)const{
if(at(x,y)!=at(p.x,p.y)) return at(x,y)<at(p.x,p.y);
return *this*p>0;
}
}p[maxn],q[maxn];
int n;
ll ans;
int main(){
n=read();
FOR(i,1,n) p[i].x=read(),p[i].y=read();
FOR(i,1,n){
FOR(j,1,n) if(j!=i) q[j-(j>i)]=p[j]-p[i];
sort(q+1,q+n);
FOR(j,1,n-1) q[j+n-1]=q[j];
ans+=1ll*(n-1)*(n-2)*(n-3)*(n-4)/24;
int cur=1;
FOR(j,1,n-1){
cur=max(cur,j);
while(cur<2*n-2 && q[cur+1]*(-q[j])>0) cur++;
int x=cur-j;
ans-=1ll*x*(x-1)*(x-2)/6;
}
}
printf("%lld
",ans);
}
F/G
不会,咕了。
以上是关于Hello 2020 题解的主要内容,如果未能解决你的问题,请参考以下文章