AtCoder Beginner Contest 373

这场咋这么简单

A. September \(\text{diff 11}\)

给你 \(12\) 个字符串 \(S_1\)\(S_{12}\),问你有多少字符串满足 \(|S_i|=i\)

点击查看代码
using namespace reader;
string s[13];
int main(){
	for(int i=1;i<=12;++i){
		cin>>s[i];
	}
	int ans=0;
	for(int i=1;i<=12;++i){
		if(s[i].length()==i){
			ans++;
		}
	}
	cout<<ans<<endl;
}

B. 1D Keyboard \(\text{diff 54}\)

一个线性排列的键盘,给你所有字母的位置,问你用一根手指打出 ABCDEFGHIJKLMNOPQRSTUVWXYZ 需要的最小移动键位数

点击查看代码
using namespace reader;
#define int long long
string s;
int locate[1256];
signed main(){
	cin>>s;
	int ans=0;
	for(int i=0;i<=(int)s.length()-1;++i){
		locate[s[i]]=i;
	}
	for(int i='A';i<'Z';++i){
		ans+=abs(locate[i]-=locate[i+1]);
	}
	cout<<ans<<endl;
}

C. Max Ai+Bj \(\text{diff 75}\)

给定两个序列 \(A,B\),任意选定 \(i,j\),求 \(\max(A_i+B_j)\)

点击查看代码
using namespace reader;
#define int long long
int n;
int a[5000001],b[5000001];
signed main(){
	read(n);
	for(int i=1;i<=n;++i){
		read(a[i]);
	}
	for(int i=1;i<=n;++i){
		read(b[i]);
	}
	sort(a+1,a+n+1);
	sort(b+1,b+n+1);
	cout<<a[n]+b[n]<<endl;
}

D. Hidden Weights \(\text{diff 765}\)

一个有向图,每条边 \(i\) 都有其权值 \(w_i\),连接节点 \(u_i,v_i\),询问一种点权 \(x_i\) 的构造方案,使得对于任意边 \(i\),都满足 \(x_{v_i}-x_{u_i}=w_i\)

\(N\le 2\times 10^5\),保证有解

注意到,你可以直接设一个点是 \(0\),然后从这个点出发开始搜,对搜到的目标点赋成 \(x_{u_i}+w_i\) 的权值

但是这题是有向图,这意味着你从某一个点出发很可能搜不到连通块内所有的节点,这就会错,所以考虑把这道题变成无向图

无向图也很简单,你只需要对所有的 \((u_i,v_i,w_i)\),建一条 \((v_i,u_i,-w_i)\) 的反边即可

注意图不联通的情况

点击查看代码
using namespace reader;
#define int long long
int n,m;
struct edge{
	int to,w;
};
vector<edge>e[200001];
bool vis[200001];
int dx[200001];
void dfs(int now,int _dx){
	dx[now]=_dx;
	vis[now]=true;
	for(edge i:e[now]){
		if(!vis[i.to]){
			dfs(i.to,_dx+i.w);
		}
	}
}
signed main(){
	read(n,m);
	for(int i=1;i<=m;++i){
		int x,y,z;
		read(x,y,z);
		e[x].push_back({y,z});
		e[y].push_back({x,-z});
	}
	for(int i=1;i<=n;++i){
		if(!vis[i]){
			dfs(i,0);
		}
	}
	for(int i=1;i<=n;++i){
		cout<<dx[i]<<" ";
	}
	cout<<endl;
}

E. How to Win the Election \(\text{diff 1592}\)

\(N\) 个人举行选举,一个人胜出当且仅当有少于 \(M\) 个人的选票数量严格大于他
一共有 \(K\) 张选票,现在已经选出了若干张,给你当前每个人得票数量 \(A_i\)
请你对每个人判断,他至少需要从剩余选票里获得几张,才能保证胜出
无解输出 \(-1\)

\(M,N\le 2\times 10^5\)
\(K,A_i\le 10^{12}\)

对每个 \(i\) 二分答案

考虑现在正在 check \(p\) 获得 \(t\) 张选票的情况,贪心地想,为了使大于 \(i\) 的人数最多,我们应该找出小于等于 \(i\) 的人里最大的,然后依次将剩余的 \(K-\sum A_i-t\) 依次分配给这些人,让每个人正好比 \(A_p+t\)\(1\),这样是人数最大的情况,直接判断这个人数和 \(M\) 的大小关系即可

但这是 \(N^2\log\) 的做法,显然过不了,考虑对内层二分答案

\(sp=A_p+t\),只考虑不大于 \(sp\) 的人,那么从中能选出 \(r\) 个人,当且仅当

\[\sum^{r}_{j}(sp+1-A_{j})\le K-\sum A_i-t \]

\[r\times (sp+1)-\sum^{r}_{j}(A_{j})\le K-\sum A_i-t \]

右边是定值,左边做法显然,维护一个前缀和即可 check

要注意的是,不能把本来就大于 \(sp\) 的人算进二分答案里,而是要直接用一个 upper_bound 找到数量然后加到答案里,因为式子里用的是 \(\sum(sp+1-A_{j})\),而不是 \(\sum(\max(0,sp+1-A_{j}))\),所以你把大于的人算进二分答案,那么直接对选票造成负贡献就错了

然后还有就是记得从二分里把这个人自己扣掉

点击查看代码
using namespace reader;
#define int long long
int n,m,k,remain;
struct node{
	int id,a;
	bool operator <(const node &A)const{
		return a<A.a;
	}
};
node a[400001];
int sum[400002];
bool check(int i,int _ans){
	int nowa=a[i].a+_ans;
	int rem=remain-_ans,tot=0;
	int pos=upper_bound(a+1,a+n+1,node{n+1,nowa})-a;
	tot+=n-pos+1;
	if(tot>=m) return false;
	int l=1,r=pos-1,ans=-1;
	while(l<=r){
		int mid=(l+r)/2;
		if((pos-mid-(mid<=i?1:0))*(nowa+1)-(sum[mid]-sum[pos]-(mid<=i?a[i].a:0))<=rem){
			r=mid-1;
			ans=mid;
		}
		else{
			l=mid+1;
		}
	}
	if(ans==-1) return true;
	tot+=pos-ans-(ans<=i?1:0);
	return tot<m;
}
int aans[400001];
signed main(){
	read(n,m,k);remain=k;
	for(int i=1;i<=n;++i){
		read(a[i].a);
		a[i].id=i;
		remain-=a[i].a;
	}
	sort(a+1,a+n+1);
	for(int i=n;i>=1;--i){
		sum[i]=sum[i+1]+a[i].a;
	}
	for(int i=1;i<=n;++i){
		int l=0,r=remain,ans=-1;
		while(l<=r){
			int mid=(l+r)/2;
			if(check(i,mid)){
				r=mid-1;
				ans=mid;
			}
			else{
				l=mid+1;
			}
		}
		aans[a[i].id]=ans;
	}
	for(int i=1;i<=n;++i){
		cout<<aans[i]<<" ";
	}
	cout<<endl;
} 

G. No Cross Matching \(\text{diff 2377}\)

二维平面内点 \(P_i,Q_i\)\(N\) 个,请你找出一种连接方式,满足

  • \(P_i,Q_i\) 全部连接
  • 只存在 \(P_i,Q_i\) 间的连接
  • 平面内无相交线段

\(N\le 300\)

考虑为 \(Q\) 分配一个排列 \(S\),使得 \(Q_{S_i}\)\(P_i\) 连边

然后我们做类似冒泡排序的算法,枚举两条线 \(l_1,l_2\),如果 \(l_1\)\(l_2\) 相交,则交换排列中对应位置的值,即交换两条线中的 \(Q\)

形象化的图

为什么我们做冒泡排序是对的,以三条线的情况为例

抽象化的图

通过简单手摸一下可以发现,这样交换线段,当前线段不会和之前已经交换过的线段相交,所以这就符合了我们做冒泡排序的性质,每一条线段总会以最多 \(O(n)\) 次的交换排到正确的位置上

所以直接对序列做冒泡排序即可

然后是怎么 check 两条线段是否相交

观察到可以直接求两条线段斜截式,设 \(l_1:k_1x+b_1,l_2:k_2x+b_2\)\(l_1\) 端点 \((x_1,y_1)\)\((x_2,y_2)\)\(l_2\) 端点 \((x_3,y_3)\)\((x_4,y_4)\)\(y_2\ge y_1,y_4\ge y_3\)),则两条线段相交当且仅当

\[\begin{cases}k_1x_3+b_1\ge y_3\\k_1x_4+b_1\le y_4\\k_2x_1+b_2\ge y_2\\k_2x_2+b_2\le y_1\end{cases} \]

当然这只能处理能用斜截式表示的线段

对于竖线

  • 若两条线段都是竖线,横坐标相等,则判断靠上直线下端点与靠下直线上端点
  • 若两条线段都是竖线,横坐标不相等,不相交
  • 否则,求出一条直线的斜截式,找另一条直线与其交点判断即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
struct node{
	int x,y;
};
int n;
node p[501],q[501];
int ans[501];
struct line{
	long double k,b;
	inline long double cal(int x){
		return x*k+b;
	}
};
inline line line_cons(node x,node y){
	line ans;
	ans.k=(x.y-y.y)*1.0/(x.x-y.x);
	ans.b=x.y-ans.k*x.x;
	return ans;
}
inline bool iscross(node x1,node x2,node y1,node y2){
	if(x1.x==x2.x){
		if(y1.x==y2.x){
			if(x1.x==y1.x){
				return min(max(x1.y,x2.y),max(y1.y,y2.y))>=max(min(x1.y,x2.y),min(y1.y,y2.y)); 
			}
			return false;
		}
		line b=line_cons(y1,y2);
		return b.cal(x1.x)>=min(x1.y,x2.y) and b.cal(x1.x)<=max(x1.y,x2.y);
	}
	if(y1.x==y2.x){
		line a=line_cons(x1,x2);
		return a.cal(y1.x)>=min(y1.y,y2.y) and a.cal(y1.x)<=max(y1.y,y2.y);
	}
	line a=line_cons(x1,x2),b=line_cons(y1,y2);
	if((a.cal(y1.x)>=y1.y and a.cal(y2.x)<=y2.y) or (a.cal(y1.x)<=y1.y and a.cal(y2.x)>=y2.y)){
		if((b.cal(x1.x)>=x1.y and b.cal(x2.x)<=x2.y) or (b.cal(x1.x)<=x1.y and b.cal(x2.x)>=x2.y)){
			return true;
		}
	}
	return false;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>p[i].x>>p[i].y;
	}
	for(int i=1;i<=n;++i){
		cin>>q[i].x>>q[i].y;
		ans[i]=i;
	}
	while(1){
		bool flag=false;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=i-1;++j){
				if(iscross(p[i],q[ans[i]],p[j],q[ans[j]])){
					swap(ans[i],ans[j]);
					flag=true;
				}
			}
		}
		if(!flag) break;
	}
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<" ";
	}
	cout<<"\n";
}

膜拜weige

啥玩意

不给参加运动会是吧,我们 BA 有自己的运动会

BA 是一款 16+ 的 4+ 游戏

优香:

来 让歌声响亮

白子:

我们在全场最中央

合:

让我们在一起 高喊胜利口号 (嗨)

Na LiLa BaLaBa

白子/泉奈:

准备好了就出发

合:

Na lila balaba

玛丽/晴奈:

在温暖的阳光下

合:

Na lila balaba

优香:

迈着整齐的步伐

合:

来将汗水挥洒 一起加油吧

晴奈:

各就位

优香:

背包整备

晴奈/优香:

礼炮后接啦啦队

白子:

原地起飞

玛丽:

注意补水

白子/玛丽:

破坏场地记得赔

泉奈:

美食摊位

晴奈:

好多美味

泉奈/晴奈:

是谁在喊要减肥

晴奈/优香:

就算再累

白子/玛丽:

也无所谓

合:

下一个对手是谁

Lalala lalalalalala lalala lalalalalalalala

Lalala lalalalalala lalala lalalalalalala

优香:

视线在搜寻

白子:

看台上你的身影

玛丽:

有没有看清

晴奈:

我努力

泉奈:

在挥手致意

优香:

广播夹杂电流音 但我听见你

白子:

为我 加油 打气

优香:

让情绪更高涨

白子:

目标是终点更前方

玛丽:

让笑容在脸上

合:

尽情绽放

白子/泉奈:

让我陪在你身旁

玛丽/晴奈:

满怀蔚蓝色的希望

合:

让我们在一起 写下新的篇章

Na lila balaba

白子/泉奈:

准备好了就出发

合:

Na lila balaba

玛丽/晴奈:

在温暖的阳光下

合:

Na lila balaba

优香:

迈着整齐的步伐

合:

来将汗水挥洒 一起加油吧

来将汗水挥洒 一起加油吧

posted @ 2024-09-29 09:06  HaneDaniko  阅读(153)  评论(12编辑  收藏  举报